@nocobase/database 0.8.0-alpha.9 → 0.8.1-alpha.3

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 (122) hide show
  1. package/lib/collection.d.ts +7 -3
  2. package/lib/collection.js +126 -22
  3. package/lib/database.d.ts +13 -7
  4. package/lib/database.js +39 -8
  5. package/lib/decorators/must-have-filter-decorator.js +4 -0
  6. package/lib/decorators/transaction-decorator.js +1 -0
  7. package/lib/features/ReferencesMap.js +14 -9
  8. package/lib/field-repository/array-field-repository.d.ts +28 -0
  9. package/lib/field-repository/array-field-repository.js +208 -0
  10. package/lib/fields/array-field.d.ts +1 -1
  11. package/lib/fields/array-field.js +15 -11
  12. package/lib/fields/belongs-to-field.d.ts +9 -1
  13. package/lib/fields/belongs-to-field.js +21 -7
  14. package/lib/fields/belongs-to-many-field.d.ts +4 -0
  15. package/lib/fields/belongs-to-many-field.js +24 -0
  16. package/lib/fields/field.d.ts +3 -2
  17. package/lib/fields/field.js +19 -13
  18. package/lib/fields/has-many-field.d.ts +2 -1
  19. package/lib/fields/has-many-field.js +18 -10
  20. package/lib/fields/has-one-field.d.ts +1 -0
  21. package/lib/fields/has-one-field.js +22 -10
  22. package/lib/fields/index.d.ts +3 -3
  23. package/lib/fields/index.js +14 -34
  24. package/lib/fields/relation-field.d.ts +2 -1
  25. package/lib/fields/relation-field.js +16 -0
  26. package/lib/fields/set-field.d.ts +10 -0
  27. package/lib/fields/set-field.js +35 -0
  28. package/lib/fields/sort-field.d.ts +1 -1
  29. package/lib/fields/sort-field.js +1 -1
  30. package/lib/filter-match.d.ts +1 -0
  31. package/lib/filter-match.js +84 -0
  32. package/lib/filter-parser.d.ts +2 -2
  33. package/lib/index.d.ts +5 -1
  34. package/lib/index.js +56 -0
  35. package/lib/inherited-collection.d.ts +13 -0
  36. package/lib/inherited-collection.js +210 -0
  37. package/lib/inherited-map.d.ts +21 -0
  38. package/lib/inherited-map.js +203 -0
  39. package/lib/model-hook.d.ts +1 -1
  40. package/lib/model.d.ts +1 -0
  41. package/lib/model.js +47 -0
  42. package/lib/operators/array.d.ts +1 -25
  43. package/lib/operators/association.d.ts +1 -9
  44. package/lib/operators/boolean.d.ts +1 -12
  45. package/lib/operators/date.d.ts +1 -33
  46. package/lib/operators/empty.d.ts +2 -25
  47. package/lib/operators/ne.d.ts +1 -13
  48. package/lib/operators/notIn.d.ts +1 -9
  49. package/lib/options-parser.d.ts +3 -2
  50. package/lib/options-parser.js +16 -1
  51. package/lib/relation-repository/relation-repository.d.ts +2 -2
  52. package/lib/relation-repository/single-relation-repository.js +2 -2
  53. package/lib/repository.d.ts +18 -10
  54. package/lib/repository.js +172 -38
  55. package/lib/sync-runner.d.ts +4 -0
  56. package/lib/sync-runner.js +181 -0
  57. package/lib/types.d.ts +2 -2
  58. package/lib/update-associations.d.ts +1 -0
  59. package/lib/update-associations.js +21 -2
  60. package/lib/update-guard.d.ts +3 -3
  61. package/lib/utils.js +5 -0
  62. package/package.json +4 -4
  63. package/src/__tests__/bigint.test.ts +48 -0
  64. package/src/__tests__/collection.test.ts +48 -13
  65. package/src/__tests__/database.test.ts +10 -0
  66. package/src/__tests__/field-repository/array-field-repository.test.ts +94 -0
  67. package/src/__tests__/fields/set.test.ts +37 -0
  68. package/src/__tests__/filter-match.test.ts +52 -0
  69. package/src/__tests__/inhertits/collection-inherits-sync.test.ts +38 -0
  70. package/src/__tests__/inhertits/collection-inherits.test.ts +994 -0
  71. package/src/__tests__/inhertits/helper.ts +3 -0
  72. package/src/__tests__/inhertits/inherited-map.test.ts +27 -0
  73. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +2 -0
  74. package/src/__tests__/relation-repository/has-many-repository.test.ts +1 -1
  75. package/src/__tests__/repository/destroy.test.ts +122 -1
  76. package/src/__tests__/repository/update-many.test.ts +57 -0
  77. package/src/__tests__/update-association-values.test.ts +232 -0
  78. package/src/__tests__/update-associations.test.ts +6 -1
  79. package/src/collection.ts +90 -8
  80. package/src/database.ts +53 -14
  81. package/src/decorators/must-have-filter-decorator.ts +4 -0
  82. package/src/decorators/transaction-decorator.ts +1 -0
  83. package/src/features/ReferencesMap.ts +20 -9
  84. package/src/features/referential-integrity-check.ts +1 -0
  85. package/src/field-repository/array-field-repository.ts +155 -0
  86. package/src/fields/array-field.ts +4 -4
  87. package/src/fields/belongs-to-field.ts +26 -10
  88. package/src/fields/belongs-to-many-field.ts +34 -0
  89. package/src/fields/field.ts +26 -13
  90. package/src/fields/has-many-field.ts +17 -9
  91. package/src/fields/has-one-field.ts +23 -9
  92. package/src/fields/index.ts +5 -5
  93. package/src/fields/relation-field.ts +16 -0
  94. package/src/fields/set-field.ts +25 -0
  95. package/src/fields/sort-field.ts +5 -4
  96. package/src/filter-match.ts +49 -0
  97. package/src/filter-parser.ts +2 -2
  98. package/src/index.ts +5 -2
  99. package/src/inherited-collection.ts +112 -0
  100. package/src/inherited-map.ts +97 -0
  101. package/src/model-hook.ts +2 -3
  102. package/src/model.ts +43 -3
  103. package/src/operators/array.ts +1 -1
  104. package/src/operators/association.ts +1 -1
  105. package/src/operators/boolean.ts +1 -1
  106. package/src/operators/date.ts +1 -1
  107. package/src/operators/empty.ts +1 -1
  108. package/src/operators/ne.ts +1 -1
  109. package/src/operators/notIn.ts +2 -1
  110. package/src/options-parser.ts +20 -4
  111. package/src/relation-repository/relation-repository.ts +2 -2
  112. package/src/relation-repository/single-relation-repository.ts +2 -2
  113. package/src/repository.ts +144 -30
  114. package/src/sync-runner.ts +162 -0
  115. package/src/types.ts +2 -2
  116. package/src/update-associations.ts +23 -7
  117. package/src/update-guard.ts +3 -3
  118. package/src/utils.ts +5 -1
  119. package/lib/fields/sequence-field.d.ts +0 -32
  120. package/lib/fields/sequence-field.js +0 -300
  121. package/src/__tests__/fields/sequence-field.test.ts +0 -480
  122. package/src/fields/sequence-field.ts +0 -202
@@ -1,4 +1,4 @@
1
- import { FindAttributeOptions, ModelCtor, Op, Sequelize } from 'sequelize';
1
+ import { FindAttributeOptions, ModelStatic, Op, Sequelize } from 'sequelize';
2
2
  import { Collection } from './collection';
3
3
  import { Database } from './database';
4
4
  import FilterParser from './filter-parser';
@@ -15,7 +15,7 @@ export class OptionsParser {
15
15
  options: FindOptions;
16
16
  database: Database;
17
17
  collection: Collection;
18
- model: ModelCtor<any>;
18
+ model: ModelStatic<any>;
19
19
  filterParser: FilterParser;
20
20
  context: OptionsParserContext;
21
21
 
@@ -104,6 +104,13 @@ export class OptionsParser {
104
104
  return filterParams;
105
105
  }
106
106
 
107
+ protected inheritFromSubQuery(): any {
108
+ return [
109
+ Sequelize.literal(`(select relname from pg_class where pg_class.oid = "${this.collection.name}".tableoid)`),
110
+ '__tableName',
111
+ ];
112
+ }
113
+
107
114
  protected parseFields(filterParams: any) {
108
115
  const appends = this.options?.appends || [];
109
116
  const except = [];
@@ -113,6 +120,10 @@ export class OptionsParser {
113
120
  exclude: [],
114
121
  }; // out put all fields by default
115
122
 
123
+ if (this.collection.isParent()) {
124
+ attributes.include.push(this.inheritFromSubQuery());
125
+ }
126
+
116
127
  if (this.options?.fields) {
117
128
  // 将fields拆分为 attributes 和 appends
118
129
  for (const field of this.options.fields) {
@@ -121,7 +132,12 @@ export class OptionsParser {
121
132
  appends.push(field);
122
133
  } else {
123
134
  // field is model attribute, change attributes to array type
124
- if (!Array.isArray(attributes)) attributes = [];
135
+ if (!Array.isArray(attributes)) {
136
+ attributes = [];
137
+ if (this.collection.isParent()) {
138
+ attributes.push(this.inheritFromSubQuery());
139
+ }
140
+ }
125
141
 
126
142
  attributes.push(field);
127
143
  }
@@ -196,7 +212,7 @@ export class OptionsParser {
196
212
  * @param queryParams
197
213
  * @param append
198
214
  */
199
- const setInclude = (model: ModelCtor<any>, queryParams: any, append: string) => {
215
+ const setInclude = (model: ModelStatic<any>, queryParams: any, append: string) => {
200
216
  const appendFields = append.split('.');
201
217
  const appendAssociation = appendFields[0];
202
218
 
@@ -1,5 +1,5 @@
1
1
  import lodash from 'lodash';
2
- import { Association, BelongsTo, BelongsToMany, HasMany, HasOne, ModelCtor, Transaction } from 'sequelize';
2
+ import { Association, BelongsTo, BelongsToMany, HasMany, HasOne, ModelStatic, Transaction } from 'sequelize';
3
3
  import { Collection } from '../collection';
4
4
  import Database from '../database';
5
5
  import { transactionWrapperBuilder } from '../decorators/transaction-decorator';
@@ -18,7 +18,7 @@ export const transaction = transactionWrapperBuilder(function () {
18
18
  export abstract class RelationRepository {
19
19
  sourceCollection: Collection;
20
20
  association: Association;
21
- targetModel: ModelCtor<any>;
21
+ targetModel: ModelStatic<any>;
22
22
  targetCollection: Collection;
23
23
  associationName: string;
24
24
  associationField: RelationField;
@@ -1,10 +1,10 @@
1
- import lodash, { omit } from 'lodash';
1
+ import lodash from 'lodash';
2
2
  import { SingleAssociationAccessors, Transactionable } from 'sequelize';
3
3
  import { Model } from '../model';
4
4
  import { Appends, Except, Fields, Filter, TargetKey, UpdateOptions } from '../repository';
5
5
  import { updateModelByValues } from '../update-associations';
6
- import { RelationRepository, transaction } from './relation-repository';
7
6
  import { handleAppendsQuery } from '../utils';
7
+ import { RelationRepository, transaction } from './relation-repository';
8
8
 
9
9
  export interface SingleRelationFindOption extends Transactionable {
10
10
  fields?: Fields;
package/src/repository.ts CHANGED
@@ -7,17 +7,18 @@ import {
7
7
  DestroyOptions as SequelizeDestroyOptions,
8
8
  FindAndCountOptions as SequelizeAndCountOptions,
9
9
  FindOptions as SequelizeFindOptions,
10
- ModelCtor,
10
+ ModelStatic,
11
11
  Op,
12
12
  Transactionable,
13
13
  UpdateOptions as SequelizeUpdateOptions,
14
+ WhereOperators,
14
15
  } from 'sequelize';
15
- import { WhereOperators } from 'sequelize/types/lib/model';
16
16
  import { Collection } from './collection';
17
17
  import { Database } from './database';
18
18
  import mustHaveFilter from './decorators/must-have-filter-decorator';
19
19
  import { transactionWrapperBuilder } from './decorators/transaction-decorator';
20
- import { RelationField } from './fields';
20
+ import { ArrayFieldRepository } from './field-repository/array-field-repository';
21
+ import { ArrayField, RelationField } from './fields';
21
22
  import FilterParser from './filter-parser';
22
23
  import { Model } from './model';
23
24
  import operators from './operators';
@@ -86,10 +87,11 @@ export type AssociationKeysToBeUpdate = string[];
86
87
 
87
88
  export type Values = any;
88
89
 
89
- export interface CountOptions extends Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'>, Transactionable {
90
- filter?: Filter;
91
- context?: any;
92
- }
90
+ export type CountOptions = Omit<SequelizeCountOptions, 'distinct' | 'where' | 'include'> &
91
+ Transactionable & {
92
+ filter?: Filter;
93
+ context?: any;
94
+ } & FilterByTk;
93
95
 
94
96
  export interface FilterByTk {
95
97
  filterByTk?: TargetKey;
@@ -135,6 +137,10 @@ export interface UpdateOptions extends Omit<SequelizeUpdateOptions, 'where'> {
135
137
  context?: any;
136
138
  }
137
139
 
140
+ interface UpdateManyOptions extends UpdateOptions {
141
+ records: Values[];
142
+ }
143
+
138
144
  interface RelatedQueryOptions {
139
145
  database: Database;
140
146
  field: RelationField;
@@ -157,19 +163,29 @@ const transaction = transactionWrapperBuilder(function () {
157
163
  class RelationRepositoryBuilder<R extends RelationRepository> {
158
164
  collection: Collection;
159
165
  associationName: string;
160
- association: Association;
166
+ association: Association | { associationType: string };
161
167
 
162
168
  builderMap = {
163
169
  HasOne: HasOneRepository,
164
170
  BelongsTo: BelongsToRepository,
165
171
  BelongsToMany: BelongsToManyRepository,
166
172
  HasMany: HasManyRepository,
173
+ ArrayField: ArrayFieldRepository,
167
174
  };
168
175
 
169
176
  constructor(collection: Collection, associationName: string) {
170
177
  this.collection = collection;
171
178
  this.associationName = associationName;
172
179
  this.association = this.collection.model.associations[this.associationName];
180
+
181
+ if (!this.association) {
182
+ const field = collection.getField(associationName);
183
+ if (field && field instanceof ArrayField) {
184
+ this.association = {
185
+ associationType: 'ArrayField',
186
+ };
187
+ }
188
+ }
173
189
  }
174
190
 
175
191
  protected builder() {
@@ -187,7 +203,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
187
203
  {
188
204
  database: Database;
189
205
  collection: Collection;
190
- model: ModelCtor<Model>;
206
+ model: ModelStatic<Model>;
191
207
 
192
208
  constructor(collection: Collection) {
193
209
  this.database = collection.context.database;
@@ -210,12 +226,32 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
210
226
  };
211
227
  }
212
228
 
213
- const count = await this.collection.model.count({
229
+ if (countOptions?.filterByTk) {
230
+ options['where'] = {
231
+ [Op.and]: [
232
+ options['where'] || {},
233
+ {
234
+ [this.collection.filterTargetKey]: options.filterByTk,
235
+ },
236
+ ],
237
+ };
238
+ }
239
+
240
+ const queryOptions: any = {
214
241
  ...options,
215
- distinct: true,
242
+ distinct: Boolean(this.collection.model.primaryKeyAttribute),
243
+ };
244
+
245
+ if (queryOptions.include?.length === 0) {
246
+ delete queryOptions.include;
247
+ }
248
+
249
+ const count = await this.collection.model.count({
250
+ ...queryOptions,
216
251
  transaction,
217
252
  });
218
253
 
254
+ // @ts-ignore
219
255
  return count;
220
256
  }
221
257
 
@@ -232,6 +268,8 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
232
268
  ...this.buildQueryOptions(options),
233
269
  };
234
270
 
271
+ let rows;
272
+
235
273
  if (opts.include && opts.include.length > 0) {
236
274
  // @ts-ignore
237
275
  const primaryKeyField = model.primaryKeyField || model.primaryKeyAttribute;
@@ -243,7 +281,12 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
243
281
  attributes: [primaryKeyField],
244
282
  group: `${model.name}.${primaryKeyField}`,
245
283
  transaction,
246
- })
284
+ include: opts.include.filter((include) => {
285
+ return (
286
+ Object.keys(include.where || {}).length > 0 || JSON.stringify(opts?.filter)?.includes(include.association)
287
+ );
288
+ }),
289
+ } as any)
247
290
  ).map((row) => {
248
291
  return { row, pk: row.get(primaryKeyField) };
249
292
  });
@@ -252,33 +295,54 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
252
295
  return [];
253
296
  }
254
297
 
298
+ const templateModel = await model.findOne({
299
+ ...opts,
300
+ includeIgnoreAttributes: false,
301
+ attributes: [primaryKeyField],
302
+ group: `${model.name}.${primaryKeyField}`,
303
+ transaction,
304
+ limit: 1,
305
+ offset: 0,
306
+ } as any);
307
+
255
308
  const where = {
256
309
  [primaryKeyField]: {
257
310
  [Op.in]: ids.map((id) => id['pk']),
258
311
  },
259
312
  };
260
313
 
261
- return await handleAppendsQuery({
314
+ rows = await handleAppendsQuery({
262
315
  queryPromises: opts.include.map((include) => {
263
- return model
264
- .findAll({
265
- ...omit(opts, ['limit', 'offset']),
266
- include: include,
267
- where,
268
- transaction,
269
- })
270
- .then((rows) => {
271
- return { rows, include };
272
- });
316
+ const options = {
317
+ ...omit(opts, ['limit', 'offset']),
318
+ include: include,
319
+ where,
320
+ transaction,
321
+ };
322
+
323
+ return model.findAll(options).then((rows) => {
324
+ return { rows, include };
325
+ });
273
326
  }),
274
- templateModel: ids[0].row,
327
+ templateModel: templateModel,
328
+ });
329
+ } else {
330
+ rows = await model.findAll({
331
+ ...opts,
332
+ transaction,
275
333
  });
276
334
  }
277
335
 
278
- return await model.findAll({
279
- ...opts,
280
- transaction,
281
- });
336
+ if (this.collection.isParent()) {
337
+ for (const row of rows) {
338
+ const rowCollectionName = this.database.tableNameCollectionMap.get(row.get('__tableName')).name;
339
+ row.set('__collection', rowCollectionName, {
340
+ raw: true,
341
+ });
342
+ }
343
+ }
344
+
345
+ return rows;
282
346
  }
283
347
 
284
348
  /**
@@ -338,7 +402,6 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
338
402
  const guard = UpdateGuard.fromOptions(this.model, { ...options, action: 'create' });
339
403
  const values = guard.sanitize(options.values || {});
340
404
 
341
-
342
405
  const instance = await this.model.create<any>(values, {
343
406
  ...options,
344
407
  transaction,
@@ -397,7 +460,14 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
397
460
  @transaction()
398
461
  @mustHaveFilter()
399
462
  async update(options: UpdateOptions & { forceUpdate?: boolean }) {
463
+ if (Array.isArray(options.values)) {
464
+ return this.updateMany({
465
+ ...options,
466
+ records: options.values,
467
+ });
468
+ }
400
469
  const transaction = await this.getTransaction(options);
470
+
401
471
  const guard = UpdateGuard.fromOptions(this.model, options);
402
472
 
403
473
  const values = guard.sanitize(options.values);
@@ -434,6 +504,24 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
434
504
  return instances;
435
505
  }
436
506
 
507
+ @transaction()
508
+ async updateMany(options: UpdateManyOptions) {
509
+ const transaction = await this.getTransaction(options);
510
+ const { records } = options;
511
+ const instances = [];
512
+
513
+ for (const values of records) {
514
+ const filterByTk = values[this.model.primaryKeyAttribute];
515
+ if (!filterByTk) {
516
+ throw new Error('filterByTk invalid');
517
+ }
518
+ const instance = await this.update({ values, filterByTk, transaction });
519
+ instances.push(instance);
520
+ }
521
+
522
+ return instances;
523
+ }
524
+
437
525
  @transaction((args, transaction) => {
438
526
  return {
439
527
  filterByTk: args[0],
@@ -456,6 +544,18 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
456
544
  ? [options.filterByTk]
457
545
  : (options.filterByTk as TargetKey[] | undefined);
458
546
 
547
+ if (
548
+ this.collection.model.primaryKeyAttributes.length !== 1 &&
549
+ filterByTk &&
550
+ !lodash.get(this.collection.options, 'filterTargetKey')
551
+ ) {
552
+ if (this.collection.model.primaryKeyAttributes.length > 1) {
553
+ throw new Error(`filterByTk is not supported for composite primary key`);
554
+ } else {
555
+ throw new Error(`filterByTk is not supported for collection that has no primary key`);
556
+ }
557
+ }
558
+
459
559
  if (filterByTk && !options.filter) {
460
560
  return await this.model.destroy({
461
561
  ...options,
@@ -469,6 +569,20 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
469
569
  }
470
570
 
471
571
  if (options.filter) {
572
+ if (
573
+ this.collection.model.primaryKeyAttributes.length !== 1 &&
574
+ !lodash.get(this.collection.options, 'filterTargetKey')
575
+ ) {
576
+ const queryOptions = {
577
+ ...this.buildQueryOptions(options),
578
+ };
579
+
580
+ return await this.model.destroy({
581
+ ...queryOptions,
582
+ transaction,
583
+ });
584
+ }
585
+
472
586
  let pks = (
473
587
  await this.find({
474
588
  filter: options.filter,
@@ -506,7 +620,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
506
620
  return new RelationRepositoryBuilder<R>(this.collection, association);
507
621
  }
508
622
 
509
- protected buildQueryOptions(options: any) {
623
+ public buildQueryOptions(options: any) {
510
624
  const parser = new OptionsParser(options, {
511
625
  collection: this.collection,
512
626
  });
@@ -0,0 +1,162 @@
1
+ import { InheritedCollection } from './inherited-collection';
2
+ import lodash from 'lodash';
3
+ import { Sequelize } from 'sequelize';
4
+
5
+ export class SyncRunner {
6
+ static async syncInheritModel(model: any, options: any) {
7
+ const { transaction } = options;
8
+
9
+ const inheritedCollection = model.collection as InheritedCollection;
10
+ const db = inheritedCollection.context.database;
11
+ const dialect = db.sequelize.getDialect();
12
+
13
+ const queryInterface = db.sequelize.getQueryInterface();
14
+
15
+ if (dialect != 'postgres') {
16
+ throw new Error('Inherit model is only supported on postgres');
17
+ }
18
+
19
+ const parents = inheritedCollection.parents;
20
+
21
+ if (!parents) {
22
+ throw new Error(
23
+ `Inherit model ${inheritedCollection.name} can't be created without parents, parents option is ${lodash
24
+ .castArray(inheritedCollection.options.inherits)
25
+ .join(', ')}`,
26
+ );
27
+ }
28
+
29
+ const parentTables = parents.map((parent) => parent.model.tableName);
30
+
31
+ const tableName = model.getTableName();
32
+
33
+ const attributes = model.tableAttributes;
34
+
35
+ const childAttributes = lodash.pickBy(attributes, (value) => {
36
+ return !value.inherit;
37
+ });
38
+
39
+ let maxSequenceVal = 0;
40
+ let maxSequenceName;
41
+
42
+ if (childAttributes.id && childAttributes.id.autoIncrement) {
43
+ for (const parent of parentTables) {
44
+ const sequenceNameResult = await queryInterface.sequelize.query(
45
+ `SELECT column_default FROM information_schema.columns WHERE
46
+ table_name='${parent}' and "column_name" = 'id';`,
47
+ {
48
+ transaction,
49
+ },
50
+ );
51
+
52
+ if (!sequenceNameResult[0].length) {
53
+ continue;
54
+ }
55
+
56
+ const columnDefault = sequenceNameResult[0][0]['column_default'];
57
+
58
+ if (!columnDefault) {
59
+ throw new Error(`Can't find sequence name of ${parent}`);
60
+ }
61
+
62
+ const regex = new RegExp(/nextval\('("?\w+"?)\'.*\)/);
63
+ const match = regex.exec(columnDefault);
64
+
65
+ const sequenceName = match[1];
66
+
67
+ const sequenceCurrentValResult = await queryInterface.sequelize.query(
68
+ `select last_value from ${sequenceName}`,
69
+ {
70
+ transaction,
71
+ },
72
+ );
73
+
74
+ const sequenceCurrentVal = parseInt(sequenceCurrentValResult[0][0]['last_value']);
75
+
76
+ if (sequenceCurrentVal > maxSequenceVal) {
77
+ maxSequenceName = sequenceName;
78
+ maxSequenceVal = sequenceCurrentVal;
79
+ }
80
+ }
81
+ }
82
+
83
+ await this.createTable(tableName, childAttributes, options, model, parentTables);
84
+
85
+ if (maxSequenceName) {
86
+ const parentsDeep = Array.from(db.inheritanceMap.getParents(inheritedCollection.name)).map(
87
+ (parent) => db.getCollection(parent).model.tableName,
88
+ );
89
+
90
+ const sequenceTables = [...parentsDeep, tableName];
91
+
92
+ for (const sequenceTable of sequenceTables) {
93
+ const queryName = Boolean(sequenceTable.match(/[A-Z]/)) ? `"${sequenceTable}"` : sequenceTable;
94
+
95
+ const idColumnQuery = await queryInterface.sequelize.query(
96
+ `
97
+ SELECT true
98
+ FROM pg_attribute
99
+ WHERE attrelid = '${queryName}'::regclass -- cast to a registered class (table)
100
+ AND attname = 'id'
101
+ AND NOT attisdropped
102
+ `,
103
+ {
104
+ transaction,
105
+ },
106
+ );
107
+
108
+ if (idColumnQuery[0].length == 0) {
109
+ continue;
110
+ }
111
+
112
+ await queryInterface.sequelize.query(
113
+ `alter table "${sequenceTable}" alter column id set default nextval('${maxSequenceName}')`,
114
+ {
115
+ transaction,
116
+ },
117
+ );
118
+ }
119
+ }
120
+
121
+ if (options.alter) {
122
+ const columns = await queryInterface.describeTable(tableName, options);
123
+
124
+ for (const columnName in childAttributes) {
125
+ if (!columns[columnName]) {
126
+ await queryInterface.addColumn(tableName, columnName, childAttributes[columnName], options);
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ static async createTable(tableName, attributes, options, model, parentTables) {
133
+ let sql = '';
134
+
135
+ options = { ...options };
136
+
137
+ if (options && options.uniqueKeys) {
138
+ lodash.forOwn(options.uniqueKeys, (uniqueKey) => {
139
+ if (uniqueKey.customIndex === undefined) {
140
+ uniqueKey.customIndex = true;
141
+ }
142
+ });
143
+ }
144
+
145
+ if (model) {
146
+ options.uniqueKeys = options.uniqueKeys || model.uniqueKeys;
147
+ }
148
+
149
+ const queryGenerator = model.queryGenerator;
150
+
151
+ attributes = lodash.mapValues(attributes, (attribute) => model.sequelize.normalizeAttribute(attribute));
152
+
153
+ attributes = queryGenerator.attributesToSQL(attributes, { table: tableName, context: 'createTable' });
154
+
155
+ sql = `${queryGenerator.createTableQuery(tableName, attributes, options)}`.replace(
156
+ ';',
157
+ ` INHERITS (${parentTables.map((t) => `"${t}"`).join(', ')});`,
158
+ );
159
+
160
+ return await model.sequelize.query(sql, options);
161
+ }
162
+ }
package/src/types.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Model } from './model';
2
- import type { ValidationOptions } from 'sequelize/types/lib/instance-validator';
3
- import type { HookReturn } from 'sequelize/types/lib/hooks';
4
2
  import type { CreateOptions, DestroyOptions, SaveOptions, SyncOptions, UpdateOptions } from 'sequelize/types';
5
3
  import { Collection, CollectionOptions } from './collection';
4
+ import { HookReturn } from 'sequelize/types/hooks';
5
+ import { ValidationOptions } from 'sequelize/types/instance-validator';
6
6
 
7
7
  export type CollectionNameType = string;
8
8
 
@@ -5,8 +5,8 @@ import {
5
5
  HasMany,
6
6
  HasOne,
7
7
  Hookable,
8
- ModelCtor,
9
- Transactionable
8
+ ModelStatic,
9
+ Transactionable,
10
10
  } from 'sequelize';
11
11
  import { Model } from './model';
12
12
  import { UpdateGuard } from './update-guard';
@@ -64,6 +64,7 @@ interface UpdateAssociationOptions extends Transactionable, Hookable {
64
64
  sourceModel?: Model;
65
65
  context?: any;
66
66
  associationContext?: any;
67
+ recursive?: boolean;
67
68
  }
68
69
 
69
70
  export async function updateModelByValues(instance: Model, values: UpdateValue, options?: UpdateOptions) {
@@ -120,6 +121,10 @@ export async function updateAssociations(instance: Model, values: any, options:
120
121
  return;
121
122
  }
122
123
 
124
+ if (options?.updateAssociationValues) {
125
+ options.recursive = true;
126
+ }
127
+
123
128
  let newTransaction = false;
124
129
  let transaction = options.transaction;
125
130
 
@@ -262,7 +267,7 @@ export async function updateSingleAssociation(
262
267
  throw new Error(`The value of '${key}' cannot be in array format`);
263
268
  }
264
269
 
265
- const { context, updateAssociationValues = [], transaction } = options;
270
+ const { recursive, context, updateAssociationValues = [], transaction } = options;
266
271
  const keys = getKeysByPrefix(updateAssociationValues, key);
267
272
 
268
273
  try {
@@ -292,13 +297,13 @@ export async function updateSingleAssociation(
292
297
 
293
298
  const createAccessor = association.accessors.create;
294
299
  let dataKey: string;
295
- let M: ModelCtor<Model>;
300
+ let M: ModelStatic<Model>;
296
301
  if (association.associationType === 'BelongsTo') {
297
- M = association.target as ModelCtor<Model>;
302
+ M = association.target as ModelStatic<Model>;
298
303
  // @ts-ignore
299
304
  dataKey = association.targetKey;
300
305
  } else {
301
- M = association.target as ModelCtor<Model>;
306
+ M = association.target as ModelStatic<Model>;
302
307
  dataKey = M.primaryKeyAttribute;
303
308
  }
304
309
 
@@ -313,6 +318,10 @@ export async function updateSingleAssociation(
313
318
  if (instance) {
314
319
  await model[setAccessor](instance, { context, transaction });
315
320
 
321
+ if (!recursive) {
322
+ return;
323
+ }
324
+
316
325
  if (updateAssociationValues.includes(key)) {
317
326
  await instance.update(value, { ...options, transaction });
318
327
  }
@@ -368,13 +377,14 @@ export async function updateMultipleAssociation(
368
377
  return false;
369
378
  }
370
379
 
371
- const { context, updateAssociationValues = [], transaction } = options;
380
+ const { recursive, context, updateAssociationValues = [], transaction } = options;
372
381
  const keys = getKeysByPrefix(updateAssociationValues, key);
373
382
 
374
383
  try {
375
384
  const setAccessor = association.accessors.set;
376
385
 
377
386
  const createAccessor = association.accessors.create;
387
+
378
388
  if (isUndefinedOrNull(value)) {
379
389
  await model[setAccessor](null, { transaction, context });
380
390
  model.setDataValue(key, null);
@@ -442,9 +452,15 @@ export async function updateMultipleAssociation(
442
452
  const instance = await association.target.findByPk<any>(item[pk], {
443
453
  transaction,
444
454
  });
455
+ if (!instance) {
456
+ continue;
457
+ }
445
458
  const addAccessor = association.accessors.add;
446
459
 
447
460
  await model[addAccessor](item[pk], accessorOptions);
461
+ if (!recursive) {
462
+ continue;
463
+ }
448
464
  if (updateAssociationValues.includes(key)) {
449
465
  await instance.update(item, { ...options, transaction });
450
466
  }
@@ -1,5 +1,5 @@
1
1
  import lodash from 'lodash';
2
- import { ModelCtor } from 'sequelize';
2
+ import { ModelStatic } from 'sequelize';
3
3
  import { Model } from './model';
4
4
  import { AssociationKeysToBeUpdate, BlackList, WhiteList } from './repository';
5
5
 
@@ -11,7 +11,7 @@ type UpdateValues = {
11
11
 
12
12
  type UpdateAction = 'create' | 'update';
13
13
  export class UpdateGuard {
14
- model: ModelCtor<any>;
14
+ model: ModelStatic<any>;
15
15
  action: UpdateAction;
16
16
  private associationKeysToBeUpdate: AssociationKeysToBeUpdate;
17
17
  private blackList: BlackList;
@@ -21,7 +21,7 @@ export class UpdateGuard {
21
21
  this.action = action;
22
22
  }
23
23
 
24
- setModel(model: ModelCtor<any>) {
24
+ setModel(model: ModelStatic<any>) {
25
25
  this.model = model;
26
26
  }
27
27