@nocobase/database 0.9.0-alpha.2 → 0.9.1-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 (103) hide show
  1. package/lib/collection-importer.js +1 -1
  2. package/lib/collection.d.ts +7 -1
  3. package/lib/collection.js +135 -61
  4. package/lib/database-utils/index.d.ts +8 -0
  5. package/lib/database-utils/index.js +59 -0
  6. package/lib/database.d.ts +26 -3
  7. package/lib/database.js +224 -61
  8. package/lib/fields/array-field.d.ts +1 -1
  9. package/lib/fields/array-field.js +2 -2
  10. package/lib/fields/belongs-to-many-field.js +1 -2
  11. package/lib/fields/field.d.ts +1 -0
  12. package/lib/fields/field.js +37 -15
  13. package/lib/fields/has-one-field.d.ts +1 -1
  14. package/lib/fields/has-one-field.js +9 -5
  15. package/lib/fields/number-field.d.ts +9 -6
  16. package/lib/fields/number-field.js +8 -6
  17. package/lib/fields/sort-field.js +15 -1
  18. package/lib/index.d.ts +6 -4
  19. package/lib/index.js +59 -36
  20. package/lib/mock-database.d.ts +2 -0
  21. package/lib/mock-database.js +3 -1
  22. package/lib/model.js +10 -1
  23. package/lib/options-parser.js +3 -0
  24. package/lib/relation-repository/belongs-to-many-repository.js +4 -2
  25. package/lib/repository.js +5 -2
  26. package/lib/sync-runner.d.ts +1 -1
  27. package/lib/sync-runner.js +28 -18
  28. package/lib/types.d.ts +7 -1
  29. package/lib/update-associations.js +17 -3
  30. package/lib/update-guard.d.ts +1 -0
  31. package/lib/update-guard.js +6 -0
  32. package/lib/utils.d.ts +5 -0
  33. package/lib/utils.js +68 -0
  34. package/lib/value-parsers/array-value-parser.d.ts +8 -0
  35. package/lib/value-parsers/array-value-parser.js +76 -0
  36. package/lib/value-parsers/base-value-parser.d.ts +12 -0
  37. package/lib/value-parsers/base-value-parser.js +59 -0
  38. package/lib/value-parsers/boolean-value-parser.d.ts +4 -0
  39. package/lib/value-parsers/boolean-value-parser.js +46 -0
  40. package/lib/value-parsers/date-value-parser.d.ts +5 -0
  41. package/lib/value-parsers/date-value-parser.js +91 -0
  42. package/lib/value-parsers/index.d.ts +12 -0
  43. package/lib/value-parsers/index.js +102 -0
  44. package/lib/value-parsers/json-value-parser.d.ts +4 -0
  45. package/lib/value-parsers/json-value-parser.js +37 -0
  46. package/lib/value-parsers/number-value-parser.d.ts +4 -0
  47. package/lib/value-parsers/number-value-parser.js +49 -0
  48. package/lib/value-parsers/string-value-parser.d.ts +8 -0
  49. package/lib/value-parsers/string-value-parser.js +76 -0
  50. package/lib/value-parsers/to-many-value-parser.d.ts +13 -0
  51. package/lib/value-parsers/to-many-value-parser.js +169 -0
  52. package/lib/value-parsers/to-one-value-parser.d.ts +4 -0
  53. package/lib/value-parsers/to-one-value-parser.js +49 -0
  54. package/package.json +4 -3
  55. package/src/__tests__/bigint.test.ts +1 -1
  56. package/src/__tests__/collection-importer.test.ts +13 -1
  57. package/src/__tests__/collection.test.ts +19 -9
  58. package/src/__tests__/database.test.ts +32 -0
  59. package/src/__tests__/fields/sort-field.test.ts +23 -0
  60. package/src/__tests__/inhertits/collection-inherits.test.ts +7 -5
  61. package/src/__tests__/percent2float.test.ts +14 -0
  62. package/src/__tests__/postgres/schema.test.ts +120 -0
  63. package/src/__tests__/underscored-options.test.ts +207 -0
  64. package/src/__tests__/update-associations-through.test.ts +73 -0
  65. package/src/__tests__/value-parsers/base.test.ts +20 -0
  66. package/src/__tests__/value-parsers/date.test.ts +67 -0
  67. package/src/__tests__/value-parsers/number.test.ts +46 -0
  68. package/src/__tests__/value-parsers/to-many.test.ts +206 -0
  69. package/src/__tests__/value-parsers/to-one.test.ts +60 -0
  70. package/src/collection-importer.ts +2 -2
  71. package/src/collection.ts +97 -15
  72. package/src/database-utils/index.ts +38 -0
  73. package/src/database.ts +171 -33
  74. package/src/fields/array-field.ts +1 -1
  75. package/src/fields/belongs-to-field.ts +1 -1
  76. package/src/fields/belongs-to-many-field.ts +0 -1
  77. package/src/fields/field.ts +45 -16
  78. package/src/fields/has-many-field.ts +1 -1
  79. package/src/fields/has-one-field.ts +11 -7
  80. package/src/fields/number-field.ts +10 -6
  81. package/src/fields/sort-field.ts +13 -1
  82. package/src/index.ts +7 -4
  83. package/src/inherited-collection.ts +1 -0
  84. package/src/mock-database.ts +3 -1
  85. package/src/model.ts +11 -2
  86. package/src/options-parser.ts +5 -0
  87. package/src/relation-repository/belongs-to-many-repository.ts +4 -2
  88. package/src/repository.ts +8 -3
  89. package/src/sync-runner.ts +33 -19
  90. package/src/types.ts +12 -1
  91. package/src/update-associations.ts +12 -5
  92. package/src/update-guard.ts +6 -0
  93. package/src/utils.ts +94 -0
  94. package/src/value-parsers/array-value-parser.ts +30 -0
  95. package/src/value-parsers/base-value-parser.ts +40 -0
  96. package/src/value-parsers/boolean-value-parser.ts +29 -0
  97. package/src/value-parsers/date-value-parser.ts +38 -0
  98. package/src/value-parsers/index.ts +46 -0
  99. package/src/value-parsers/json-value-parser.ts +19 -0
  100. package/src/value-parsers/number-value-parser.ts +29 -0
  101. package/src/value-parsers/string-value-parser.ts +31 -0
  102. package/src/value-parsers/to-many-value-parser.ts +85 -0
  103. package/src/value-parsers/to-one-value-parser.ts +20 -0
package/src/collection.ts CHANGED
@@ -3,17 +3,17 @@ import { EventEmitter } from 'events';
3
3
  import { default as lodash, default as _ } from 'lodash';
4
4
  import {
5
5
  ModelOptions,
6
+ ModelStatic,
6
7
  QueryInterfaceDropTableOptions,
7
8
  SyncOptions,
8
9
  Transactionable,
9
- ModelStatic,
10
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
+ import { checkIdentifier, md5, snakeCase } from './utils';
17
17
 
18
18
  export type RepositoryType = typeof Repository;
19
19
 
@@ -36,6 +36,7 @@ export interface CollectionOptions extends Omit<ModelOptions, 'name' | 'hooks'>
36
36
  * @default 'options'
37
37
  */
38
38
  magicAttribute?: string;
39
+
39
40
  [key: string]: any;
40
41
  }
41
42
 
@@ -62,17 +63,21 @@ export class Collection<
62
63
  return this.options.name;
63
64
  }
64
65
 
66
+ get titleField() {
67
+ return (this.options.titleField as string) || this.model.primaryKeyAttribute;
68
+ }
69
+
65
70
  get db() {
66
71
  return this.context.database;
67
72
  }
68
73
 
69
74
  constructor(options: CollectionOptions, context: CollectionContext) {
70
75
  super();
71
- this.checkOptions(options);
72
-
73
76
  this.context = context;
74
77
  this.options = options;
75
78
 
79
+ this.checkOptions(options);
80
+
76
81
  this.bindFieldEventListener();
77
82
  this.modelInit();
78
83
 
@@ -89,15 +94,31 @@ export class Collection<
89
94
 
90
95
  private checkOptions(options: CollectionOptions) {
91
96
  checkIdentifier(options.name);
97
+ this.checkTableName();
92
98
  }
93
99
 
94
- private sequelizeModelOptions() {
100
+ private checkTableName() {
101
+ const tableName = this.tableName();
102
+ for (const [k, collection] of this.db.collections) {
103
+ if (collection.name != this.options.name && tableName === collection.tableName()) {
104
+ throw new Error(`collection ${collection.name} and ${this.name} have same tableName "${tableName}"`);
105
+ }
106
+ }
107
+ }
108
+
109
+ tableName() {
95
110
  const { name, tableName } = this.options;
111
+ const tName = tableName || name;
112
+ return this.db.options.underscored ? snakeCase(tName) : tName;
113
+ }
114
+
115
+ private sequelizeModelOptions() {
116
+ const { name } = this.options;
96
117
  return {
97
118
  ..._.omit(this.options, ['name', 'fields', 'model', 'targetKey']),
98
119
  modelName: name,
99
120
  sequelize: this.context.database.sequelize,
100
- tableName: tableName || name,
121
+ tableName: this.tableName(),
101
122
  };
102
123
  }
103
124
 
@@ -108,8 +129,10 @@ export class Collection<
108
129
  if (this.model) {
109
130
  return;
110
131
  }
132
+
111
133
  const { name, model, autoGenId = true } = this.options;
112
134
  let M: ModelStatic<Model> = Model;
135
+
113
136
  if (this.context.database.sequelize.isDefined(name)) {
114
137
  const m = this.context.database.sequelize.model(name);
115
138
  if ((m as any).isThrough) {
@@ -122,11 +145,13 @@ export class Collection<
122
145
  return;
123
146
  }
124
147
  }
148
+
125
149
  if (typeof model === 'string') {
126
150
  M = this.context.database.models.get(model) || Model;
127
151
  } else if (model) {
128
152
  M = model;
129
153
  }
154
+
130
155
  // @ts-ignore
131
156
  this.model = class extends M {};
132
157
  this.model.init(null, this.sequelizeModelOptions());
@@ -179,8 +204,31 @@ export class Collection<
179
204
  return this.setField(name, options);
180
205
  }
181
206
 
207
+ checkFieldType(name: string, options: FieldOptions) {
208
+ if (!this.db.options.underscored) {
209
+ return;
210
+ }
211
+ const fieldName = options.field || snakeCase(name);
212
+ const field = this.findField((f) => {
213
+ if (f.name === name) {
214
+ return false;
215
+ }
216
+ if (f.field) {
217
+ return f.field === fieldName;
218
+ }
219
+ return snakeCase(f.name) === fieldName;
220
+ });
221
+ if (!field) {
222
+ return;
223
+ }
224
+ if (options.type !== field.type) {
225
+ throw new Error(`fields with same column must be of the same type ${JSON.stringify(options)}`);
226
+ }
227
+ }
228
+
182
229
  setField(name: string, options: FieldOptions): Field {
183
230
  checkIdentifier(name);
231
+ this.checkFieldType(name, options);
184
232
 
185
233
  const { database } = this.context;
186
234
 
@@ -305,6 +353,7 @@ export class Collection<
305
353
  newOptions = merge(this.options, newOptions, mergeOptions);
306
354
 
307
355
  this.context.database.emit('beforeUpdateCollection', this, newOptions);
356
+ this.options = newOptions;
308
357
 
309
358
  this.setFields(options.fields, false);
310
359
  this.setRepository(options.repository);
@@ -357,9 +406,13 @@ export class Collection<
357
406
  if (!index) {
358
407
  return;
359
408
  }
409
+
410
+ // collection defined indexes
360
411
  let indexes: any = this.model.options.indexes || [];
412
+
361
413
  let indexName = [];
362
414
  let indexItem;
415
+
363
416
  if (typeof index === 'string') {
364
417
  indexItem = {
365
418
  fields: [index],
@@ -374,13 +427,17 @@ export class Collection<
374
427
  indexItem = index;
375
428
  indexName = index.fields;
376
429
  }
430
+
377
431
  if (lodash.isEqual(this.model.primaryKeyAttributes, indexName)) {
378
432
  return;
379
433
  }
434
+
380
435
  const name: string = this.model.primaryKeyAttributes.join(',');
436
+
381
437
  if (name.startsWith(`${indexName.join(',')},`)) {
382
438
  return;
383
439
  }
440
+
384
441
  for (const item of indexes) {
385
442
  if (lodash.isEqual(item.fields, indexName)) {
386
443
  return;
@@ -390,11 +447,13 @@ export class Collection<
390
447
  return;
391
448
  }
392
449
  }
450
+
393
451
  if (!indexItem) {
394
452
  return;
395
453
  }
454
+
396
455
  indexes.push(indexItem);
397
- this.model.options.indexes = indexes;
456
+
398
457
  const tableName = this.model.getTableName();
399
458
  // @ts-ignore
400
459
  this.model._indexes = this.model.options.indexes
@@ -406,6 +465,7 @@ export class Collection<
406
465
  }
407
466
  return item;
408
467
  });
468
+
409
469
  this.refreshIndexes();
410
470
  }
411
471
 
@@ -425,15 +485,23 @@ export class Collection<
425
485
  refreshIndexes() {
426
486
  // @ts-ignore
427
487
  const indexes: any[] = this.model._indexes;
488
+
428
489
  // @ts-ignore
429
- this.model._indexes = indexes.filter((item) => {
430
- for (const field of item.fields) {
431
- if (!this.model.rawAttributes[field]) {
432
- return false;
433
- }
434
- }
435
- return true;
436
- });
490
+ this.model._indexes = lodash.uniqBy(
491
+ indexes
492
+ .filter((item) => {
493
+ return item.fields.every((field) =>
494
+ Object.values(this.model.rawAttributes).find((fieldVal) => fieldVal.field === field),
495
+ );
496
+ })
497
+ .map((item) => {
498
+ if (this.options.underscored) {
499
+ item.fields = item.fields.map((field) => snakeCase(field));
500
+ }
501
+ return item;
502
+ }),
503
+ 'name',
504
+ );
437
505
  }
438
506
 
439
507
  async sync(syncOptions?: SyncOptions) {
@@ -470,4 +538,18 @@ export class Collection<
470
538
  public isParent() {
471
539
  return this.context.database.inheritanceMap.isParentNode(this.name);
472
540
  }
541
+
542
+ public addSchemaTableName() {
543
+ const tableName = this.model.tableName;
544
+
545
+ if (this.options.schema) {
546
+ return this.db.utils.addSchema(tableName, this.options.schema);
547
+ }
548
+
549
+ return tableName;
550
+ }
551
+
552
+ public quotedTableName() {
553
+ return this.db.utils.quoteTable(this.addSchemaTableName());
554
+ }
473
555
  }
@@ -0,0 +1,38 @@
1
+ import Database from '../database';
2
+ import lodash from 'lodash';
3
+
4
+ export default class DatabaseUtils {
5
+ constructor(public db: Database) {}
6
+
7
+ addSchema(tableName, schema?) {
8
+ if (this.db.options.schema && !schema) {
9
+ schema = this.db.options.schema;
10
+ }
11
+
12
+ if (schema) {
13
+ // @ts-ignore
14
+ tableName = this.db.sequelize.getQueryInterface().queryGenerator.addSchema({
15
+ tableName,
16
+ _schema: schema,
17
+ });
18
+ }
19
+
20
+ return tableName;
21
+ }
22
+
23
+ quoteTable(tableName) {
24
+ const queryGenerator = this.db.sequelize.getQueryInterface().queryGenerator;
25
+ // @ts-ignore
26
+ tableName = queryGenerator.quoteTable(lodash.isPlainObject(tableName) ? tableName : this.addSchema(tableName));
27
+
28
+ return tableName;
29
+ }
30
+
31
+ schema() {
32
+ if (!this.db.inDialect('postgres')) {
33
+ return undefined;
34
+ }
35
+
36
+ return this.db.options.schema || 'public';
37
+ }
38
+ }
package/src/database.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  Sequelize,
16
16
  SyncOptions,
17
17
  Transactionable,
18
- Utils
18
+ Utils,
19
19
  } from 'sequelize';
20
20
  import { SequelizeStorage, Umzug } from 'umzug';
21
21
  import { Collection, CollectionOptions, RepositoryType } from './collection';
@@ -58,8 +58,12 @@ import {
58
58
  SyncListener,
59
59
  UpdateListener,
60
60
  UpdateWithAssociationsListener,
61
- ValidateListener
61
+ ValidateListener,
62
62
  } from './types';
63
+ import { patchSequelizeQueryInterface, snakeCase } from './utils';
64
+
65
+ import DatabaseUtils from './database-utils';
66
+ import { BaseValueParser, registerFieldValueParsers } from './value-parsers';
63
67
 
64
68
  export interface MergeOptions extends merge.Options {}
65
69
 
@@ -76,6 +80,7 @@ export interface IDatabaseOptions extends Options {
76
80
  tablePrefix?: string;
77
81
  migrator?: any;
78
82
  usingBigIntForId?: boolean;
83
+ underscored?: boolean;
79
84
  }
80
85
 
81
86
  export type DatabaseOptions = IDatabaseOptions;
@@ -100,6 +105,30 @@ export type AddMigrationsOptions = {
100
105
 
101
106
  type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
102
107
 
108
+ export const DialectVersionAccessors = {
109
+ sqlite: {
110
+ sql: 'select sqlite_version() as version',
111
+ get: (v: string) => v,
112
+ },
113
+ mysql: {
114
+ sql: 'select version() as version',
115
+ get: (v: string) => {
116
+ if (v.toLowerCase().includes('mariadb')) {
117
+ return '';
118
+ }
119
+ const m = /([\d+\.]+)/.exec(v);
120
+ return m[0];
121
+ },
122
+ },
123
+ postgres: {
124
+ sql: 'select version() as version',
125
+ get: (v: string) => {
126
+ const m = /([\d+\.]+)/.exec(v);
127
+ return semver.minVersion(m[0]).version;
128
+ },
129
+ },
130
+ };
131
+
103
132
  class DatabaseVersion {
104
133
  db: Database;
105
134
 
@@ -108,33 +137,14 @@ class DatabaseVersion {
108
137
  }
109
138
 
110
139
  async satisfies(versions) {
111
- const dialects = {
112
- sqlite: {
113
- sql: 'select sqlite_version() as version',
114
- get: (v) => v,
115
- },
116
- mysql: {
117
- sql: 'select version() as version',
118
- get: (v) => {
119
- const m = /([\d+\.]+)/.exec(v);
120
- return m[0];
121
- },
122
- },
123
- postgres: {
124
- sql: 'select version() as version',
125
- get: (v) => {
126
- const m = /([\d+\.]+)/.exec(v);
127
- return semver.minVersion(m[0]).version;
128
- },
129
- },
130
- };
131
- for (const dialect of Object.keys(dialects)) {
140
+ const accessors = DialectVersionAccessors;
141
+ for (const dialect of Object.keys(accessors)) {
132
142
  if (this.db.inDialect(dialect)) {
133
143
  if (!versions?.[dialect]) {
134
144
  return false;
135
145
  }
136
- const [result] = (await this.db.sequelize.query(dialects[dialect].sql)) as any;
137
- return semver.satisfies(dialects[dialect].get(result?.[0]?.version), versions[dialect]);
146
+ const [result] = (await this.db.sequelize.query(accessors[dialect].sql)) as any;
147
+ return semver.satisfies(accessors[dialect].get(result?.[0]?.version), versions[dialect]);
138
148
  }
139
149
  }
140
150
  return false;
@@ -146,6 +156,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
146
156
  migrator: Umzug;
147
157
  migrations: Migrations;
148
158
  fieldTypes = new Map();
159
+ fieldValueParsers = new Map();
149
160
  options: IDatabaseOptions;
150
161
  models = new Map<string, ModelStatic<Model>>();
151
162
  repositories = new Map<string, RepositoryType>();
@@ -155,6 +166,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
155
166
  modelCollection = new Map<ModelStatic<any>, Collection>();
156
167
  tableNameCollectionMap = new Map<string, Collection>();
157
168
 
169
+ utils = new DatabaseUtils(this);
158
170
  referenceMap = new ReferencesMap();
159
171
  inheritanceMap = new InheritanceMap();
160
172
 
@@ -196,9 +208,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
196
208
  // https://github.com/sequelize/sequelize/issues/1774
197
209
  require('pg').defaults.parseInt8 = true;
198
210
  }
211
+ this.options = opts;
199
212
 
200
213
  this.sequelize = new Sequelize(opts);
201
- this.options = opts;
214
+
202
215
  this.collections = new Map();
203
216
  this.modelHook = new ModelHook(this);
204
217
 
@@ -211,7 +224,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
211
224
  });
212
225
 
213
226
  // register database field types
214
- for (const [name, field] of Object.entries(FieldTypes)) {
227
+ for (const [name, field] of Object.entries<any>(FieldTypes)) {
215
228
  if (['Field', 'RelationField'].includes(name)) {
216
229
  continue;
217
230
  }
@@ -222,6 +235,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
222
235
  });
223
236
  }
224
237
 
238
+ registerFieldValueParsers(this);
239
+
225
240
  this.initOperators();
226
241
 
227
242
  const migratorOptions: any = this.options.migrator || {};
@@ -250,6 +265,8 @@ export class Database extends EventEmitter implements AsyncEmitter {
250
265
  name: 'migrations',
251
266
  autoGenId: false,
252
267
  timestamps: false,
268
+ namespace: 'core',
269
+ duplicator: 'required',
253
270
  fields: [{ type: 'string', name: 'name' }],
254
271
  });
255
272
 
@@ -260,9 +277,16 @@ export class Database extends EventEmitter implements AsyncEmitter {
260
277
  });
261
278
 
262
279
  this.initListener();
280
+ patchSequelizeQueryInterface(this);
263
281
  }
264
282
 
265
283
  initListener() {
284
+ this.on('beforeDefine', (model, options) => {
285
+ if (this.options.underscored) {
286
+ options.underscored = true;
287
+ }
288
+ });
289
+
266
290
  this.on('afterCreate', async (instance) => {
267
291
  instance?.toChangedWithAssociations?.();
268
292
  });
@@ -292,6 +316,37 @@ export class Database extends EventEmitter implements AsyncEmitter {
292
316
  }
293
317
  }
294
318
  });
319
+
320
+ this.on('afterUpdateCollection', (collection, options) => {
321
+ if (collection.options.schema) {
322
+ collection.model._schema = collection.options.schema;
323
+ }
324
+ });
325
+
326
+ this.on('beforeDefineCollection', (options) => {
327
+ if (options.underscored) {
328
+ if (lodash.get(options, 'sortable.scopeKey')) {
329
+ options.sortable.scopeKey = snakeCase(options.sortable.scopeKey);
330
+ }
331
+
332
+ if (lodash.get(options, 'indexes')) {
333
+ // change index fields to snake case
334
+ options.indexes = options.indexes.map((index) => {
335
+ if (index.fields) {
336
+ index.fields = index.fields.map((field) => {
337
+ return snakeCase(field);
338
+ });
339
+ }
340
+
341
+ return index;
342
+ });
343
+ }
344
+ }
345
+
346
+ if (this.options.schema && !options.schema) {
347
+ options.schema = this.options.schema;
348
+ }
349
+ });
295
350
  }
296
351
 
297
352
  addMigration(item: MigrationItem) {
@@ -326,6 +381,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
326
381
  collection<Attributes = any, CreateAttributes = Attributes>(
327
382
  options: CollectionOptions,
328
383
  ): Collection<Attributes, CreateAttributes> {
384
+ if (this.options.underscored) {
385
+ options.underscored = true;
386
+ }
387
+
329
388
  this.emit('beforeDefineCollection', options);
330
389
 
331
390
  const hasValidInheritsOptions = (() => {
@@ -351,6 +410,31 @@ export class Database extends EventEmitter implements AsyncEmitter {
351
410
  return this.options.tablePrefix || '';
352
411
  }
353
412
 
413
+ getFieldByPath(path: string) {
414
+ if (!path) {
415
+ return;
416
+ }
417
+
418
+ const [collectionName, associationName, ...args] = path.split('.');
419
+ let collection = this.getCollection(collectionName);
420
+
421
+ if (!collection) {
422
+ return;
423
+ }
424
+
425
+ const field = collection.getField(associationName);
426
+
427
+ if (!field) {
428
+ return;
429
+ }
430
+
431
+ if (args.length > 0) {
432
+ return this.getFieldByPath(`${field?.target}.${args.join('.')}`);
433
+ }
434
+
435
+ return field;
436
+ }
437
+
354
438
  /**
355
439
  * get exists collection by its name
356
440
  * @param name
@@ -430,6 +514,20 @@ export class Database extends EventEmitter implements AsyncEmitter {
430
514
  }
431
515
  }
432
516
 
517
+ registerFieldValueParsers(parsers: MapOf<any>) {
518
+ for (const [type, parser] of Object.entries(parsers)) {
519
+ this.fieldValueParsers.set(type, parser);
520
+ }
521
+ }
522
+
523
+ buildFieldValueParser<T extends BaseValueParser>(field: Field, ctx: any) {
524
+ const Parser = this.fieldValueParsers.has(field.type)
525
+ ? this.fieldValueParsers.get(field.type)
526
+ : this.fieldValueParsers.get('default');
527
+ const parser = new Parser(field, ctx);
528
+ return parser as T;
529
+ }
530
+
433
531
  registerModels(models: MapOf<ModelStatic<any>>) {
434
532
  for (const [type, schemaType] of Object.entries(models)) {
435
533
  this.models.set(type, schemaType);
@@ -475,6 +573,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
475
573
  throw Error(`unsupported field type ${type}`);
476
574
  }
477
575
 
576
+ if (options.field && this.options.underscored) {
577
+ options.field = snakeCase(options.field);
578
+ }
579
+
478
580
  return new Field(options, context);
479
581
  }
480
582
 
@@ -484,6 +586,10 @@ export class Database extends EventEmitter implements AsyncEmitter {
484
586
  await this.sequelize.query('SET FOREIGN_KEY_CHECKS = 0', null);
485
587
  }
486
588
 
589
+ if (this.options.schema && this.inDialect('postgres')) {
590
+ await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${this.options.schema}"`, null);
591
+ }
592
+
487
593
  const result = await this.sequelize.sync(options);
488
594
 
489
595
  if (isMySQL) {
@@ -494,24 +600,49 @@ export class Database extends EventEmitter implements AsyncEmitter {
494
600
  }
495
601
 
496
602
  async clean(options: CleanOptions) {
497
- const { drop, ...others } = options;
498
- if (drop) {
499
- await this.sequelize.getQueryInterface().dropAllTables(others);
603
+ const { drop, ...others } = options || {};
604
+ if (drop !== true) {
605
+ return;
500
606
  }
607
+
608
+ if (this.options.schema) {
609
+ const tableNames = (await this.sequelize.getQueryInterface().showAllTables()).map((table) => {
610
+ return `"${this.options.schema}"."${table}"`;
611
+ });
612
+
613
+ const skip = options.skip || [];
614
+
615
+ // @ts-ignore
616
+ for (const tableName of tableNames) {
617
+ if (skip.includes(tableName)) {
618
+ continue;
619
+ }
620
+ await this.sequelize.query(`DROP TABLE IF EXISTS ${tableName} CASCADE`);
621
+ }
622
+ return;
623
+ }
624
+
625
+ await this.sequelize.getQueryInterface().dropAllTables(others);
501
626
  }
502
627
 
503
- async collectionExistsInDb(name, options?: Transactionable) {
628
+ async collectionExistsInDb(name: string, options?: Transactionable) {
629
+ const collection = this.getCollection(name);
630
+ if (!collection) {
631
+ return false;
632
+ }
633
+
504
634
  const tables = await this.sequelize.getQueryInterface().showAllTables({
505
635
  transaction: options?.transaction,
506
636
  });
507
- return !!tables.find((table) => table === `${this.getTablePrefix()}${name}`);
637
+
638
+ return tables.includes(this.getCollection(name).model.tableName);
508
639
  }
509
640
 
510
641
  public isSqliteMemory() {
511
642
  return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
512
643
  }
513
644
 
514
- async auth(options: QueryOptions & { retry?: number } = {}) {
645
+ async auth(options: Omit<QueryOptions, 'retry'> & { retry?: number | Pick<QueryOptions, 'retry'> } = {}) {
515
646
  const { retry = 10, ...others } = options;
516
647
  const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
517
648
  let count = 1;
@@ -533,6 +664,12 @@ export class Database extends EventEmitter implements AsyncEmitter {
533
664
  return await authenticate();
534
665
  }
535
666
 
667
+ async prepare() {
668
+ if (this.inDialect('postgres') && this.options.schema && this.options.schema != 'public') {
669
+ await this.sequelize.query(`CREATE SCHEMA IF NOT EXISTS "${this.options.schema}"`, null);
670
+ }
671
+ }
672
+
536
673
  async reconnect() {
537
674
  if (this.isSqliteMemory()) {
538
675
  return;
@@ -589,6 +726,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
589
726
  }
590
727
 
591
728
  extendCollection(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions) {
729
+ collectionOptions = lodash.cloneDeep(collectionOptions);
592
730
  const collectionName = collectionOptions.name;
593
731
  const existCollection = this.getCollection(collectionName);
594
732
  if (existCollection) {
@@ -1,5 +1,5 @@
1
- import { BaseColumnFieldOptions, Field } from './field';
2
1
  import { DataTypes } from 'sequelize';
2
+ import { BaseColumnFieldOptions, Field } from './field';
3
3
 
4
4
  export class ArrayField extends Field {
5
5
  get dataType() {
@@ -1,5 +1,5 @@
1
1
  import { omit } from 'lodash';
2
- import { BelongsTo, BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
2
+ import { BelongsToOptions as SequelizeBelongsToOptions, Utils } from 'sequelize';
3
3
  import { Reference } from '../features/ReferencesMap';
4
4
  import { checkIdentifier } from '../utils';
5
5
  import { BaseRelationFieldOptions, RelationField } from './relation-field';
@@ -57,7 +57,6 @@ export class BelongsToManyField extends RelationField {
57
57
  } else {
58
58
  Through = database.collection({
59
59
  name: through,
60
- // timestamps: false,
61
60
  });
62
61
 
63
62
  Object.defineProperty(Through.model, 'isThrough', { value: true });