@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.
- package/lib/collection-importer.js +1 -1
- package/lib/collection.d.ts +7 -1
- package/lib/collection.js +135 -61
- package/lib/database-utils/index.d.ts +8 -0
- package/lib/database-utils/index.js +59 -0
- package/lib/database.d.ts +26 -3
- package/lib/database.js +224 -61
- package/lib/fields/array-field.d.ts +1 -1
- package/lib/fields/array-field.js +2 -2
- package/lib/fields/belongs-to-many-field.js +1 -2
- package/lib/fields/field.d.ts +1 -0
- package/lib/fields/field.js +37 -15
- package/lib/fields/has-one-field.d.ts +1 -1
- package/lib/fields/has-one-field.js +9 -5
- package/lib/fields/number-field.d.ts +9 -6
- package/lib/fields/number-field.js +8 -6
- package/lib/fields/sort-field.js +15 -1
- package/lib/index.d.ts +6 -4
- package/lib/index.js +59 -36
- package/lib/mock-database.d.ts +2 -0
- package/lib/mock-database.js +3 -1
- package/lib/model.js +10 -1
- package/lib/options-parser.js +3 -0
- package/lib/relation-repository/belongs-to-many-repository.js +4 -2
- package/lib/repository.js +5 -2
- package/lib/sync-runner.d.ts +1 -1
- package/lib/sync-runner.js +28 -18
- package/lib/types.d.ts +7 -1
- package/lib/update-associations.js +17 -3
- package/lib/update-guard.d.ts +1 -0
- package/lib/update-guard.js +6 -0
- package/lib/utils.d.ts +5 -0
- package/lib/utils.js +68 -0
- package/lib/value-parsers/array-value-parser.d.ts +8 -0
- package/lib/value-parsers/array-value-parser.js +76 -0
- package/lib/value-parsers/base-value-parser.d.ts +12 -0
- package/lib/value-parsers/base-value-parser.js +59 -0
- package/lib/value-parsers/boolean-value-parser.d.ts +4 -0
- package/lib/value-parsers/boolean-value-parser.js +46 -0
- package/lib/value-parsers/date-value-parser.d.ts +5 -0
- package/lib/value-parsers/date-value-parser.js +91 -0
- package/lib/value-parsers/index.d.ts +12 -0
- package/lib/value-parsers/index.js +102 -0
- package/lib/value-parsers/json-value-parser.d.ts +4 -0
- package/lib/value-parsers/json-value-parser.js +37 -0
- package/lib/value-parsers/number-value-parser.d.ts +4 -0
- package/lib/value-parsers/number-value-parser.js +49 -0
- package/lib/value-parsers/string-value-parser.d.ts +8 -0
- package/lib/value-parsers/string-value-parser.js +76 -0
- package/lib/value-parsers/to-many-value-parser.d.ts +13 -0
- package/lib/value-parsers/to-many-value-parser.js +169 -0
- package/lib/value-parsers/to-one-value-parser.d.ts +4 -0
- package/lib/value-parsers/to-one-value-parser.js +49 -0
- package/package.json +4 -3
- package/src/__tests__/bigint.test.ts +1 -1
- package/src/__tests__/collection-importer.test.ts +13 -1
- package/src/__tests__/collection.test.ts +19 -9
- package/src/__tests__/database.test.ts +32 -0
- package/src/__tests__/fields/sort-field.test.ts +23 -0
- package/src/__tests__/inhertits/collection-inherits.test.ts +7 -5
- package/src/__tests__/percent2float.test.ts +14 -0
- package/src/__tests__/postgres/schema.test.ts +120 -0
- package/src/__tests__/underscored-options.test.ts +207 -0
- package/src/__tests__/update-associations-through.test.ts +73 -0
- package/src/__tests__/value-parsers/base.test.ts +20 -0
- package/src/__tests__/value-parsers/date.test.ts +67 -0
- package/src/__tests__/value-parsers/number.test.ts +46 -0
- package/src/__tests__/value-parsers/to-many.test.ts +206 -0
- package/src/__tests__/value-parsers/to-one.test.ts +60 -0
- package/src/collection-importer.ts +2 -2
- package/src/collection.ts +97 -15
- package/src/database-utils/index.ts +38 -0
- package/src/database.ts +171 -33
- package/src/fields/array-field.ts +1 -1
- package/src/fields/belongs-to-field.ts +1 -1
- package/src/fields/belongs-to-many-field.ts +0 -1
- package/src/fields/field.ts +45 -16
- package/src/fields/has-many-field.ts +1 -1
- package/src/fields/has-one-field.ts +11 -7
- package/src/fields/number-field.ts +10 -6
- package/src/fields/sort-field.ts +13 -1
- package/src/index.ts +7 -4
- package/src/inherited-collection.ts +1 -0
- package/src/mock-database.ts +3 -1
- package/src/model.ts +11 -2
- package/src/options-parser.ts +5 -0
- package/src/relation-repository/belongs-to-many-repository.ts +4 -2
- package/src/repository.ts +8 -3
- package/src/sync-runner.ts +33 -19
- package/src/types.ts +12 -1
- package/src/update-associations.ts +12 -5
- package/src/update-guard.ts +6 -0
- package/src/utils.ts +94 -0
- package/src/value-parsers/array-value-parser.ts +30 -0
- package/src/value-parsers/base-value-parser.ts +40 -0
- package/src/value-parsers/boolean-value-parser.ts +29 -0
- package/src/value-parsers/date-value-parser.ts +38 -0
- package/src/value-parsers/index.ts +46 -0
- package/src/value-parsers/json-value-parser.ts +19 -0
- package/src/value-parsers/number-value-parser.ts +29 -0
- package/src/value-parsers/string-value-parser.ts +31 -0
- package/src/value-parsers/to-many-value-parser.ts +85 -0
- 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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
return
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
|
112
|
-
|
|
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(
|
|
137
|
-
return semver.satisfies(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
1
|
import { omit } from 'lodash';
|
|
2
|
-
import {
|
|
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';
|