@nocobase/database 0.9.4-alpha.1 → 0.10.0-alpha.2

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 (47) hide show
  1. package/lib/database.js +2 -1
  2. package/lib/eager-loading/eager-loading-tree.d.ts +4 -0
  3. package/lib/eager-loading/eager-loading-tree.js +79 -16
  4. package/lib/fields/belongs-to-field.d.ts +1 -1
  5. package/lib/fields/belongs-to-field.js +3 -2
  6. package/lib/fields/belongs-to-many-field.js +1 -1
  7. package/lib/fields/has-many-field.js +1 -1
  8. package/lib/fields/has-one-field.js +1 -1
  9. package/lib/filter-parser.js +15 -9
  10. package/lib/listeners/append-child-collection-name-after-repository-find.js +5 -1
  11. package/lib/options-parser.d.ts +1 -1
  12. package/lib/options-parser.js +15 -11
  13. package/lib/relation-repository/multiple-relation-repository.js +2 -1
  14. package/lib/relation-repository/single-relation-repository.js +2 -1
  15. package/lib/repository.d.ts +12 -0
  16. package/lib/repository.js +166 -66
  17. package/lib/update-associations.js +1 -1
  18. package/lib/view/field-type-map.d.ts +2 -0
  19. package/lib/view/field-type-map.js +2 -0
  20. package/lib/view/view-inference.js +52 -26
  21. package/lib/view-collection.js +0 -1
  22. package/package.json +4 -4
  23. package/src/__tests__/eager-loading/eager-loading-tree.test.ts +149 -0
  24. package/src/__tests__/field-options/sort-by.test.ts +3 -1
  25. package/src/__tests__/filter.test.ts +54 -0
  26. package/src/__tests__/inhertits/collection-inherits.test.ts +165 -0
  27. package/src/__tests__/repository/create.test.ts +129 -6
  28. package/src/__tests__/repository/find.test.ts +11 -0
  29. package/src/__tests__/repository.test.ts +24 -0
  30. package/src/__tests__/update-associations.test.ts +109 -1
  31. package/src/__tests__/view/view-inference.test.ts +1 -0
  32. package/src/database.ts +4 -1
  33. package/src/eager-loading/eager-loading-tree.ts +92 -17
  34. package/src/fields/belongs-to-field.ts +6 -4
  35. package/src/fields/belongs-to-many-field.ts +2 -1
  36. package/src/fields/has-many-field.ts +1 -1
  37. package/src/fields/has-one-field.ts +1 -1
  38. package/src/filter-parser.ts +17 -9
  39. package/src/listeners/append-child-collection-name-after-repository-find.ts +9 -5
  40. package/src/options-parser.ts +25 -19
  41. package/src/relation-repository/multiple-relation-repository.ts +1 -0
  42. package/src/relation-repository/single-relation-repository.ts +1 -0
  43. package/src/repository.ts +84 -0
  44. package/src/update-associations.ts +3 -0
  45. package/src/view/field-type-map.ts +2 -0
  46. package/src/view/view-inference.ts +75 -43
  47. package/src/view-collection.ts +0 -1
package/src/repository.ts CHANGED
@@ -32,6 +32,7 @@ import { RelationRepository } from './relation-repository/relation-repository';
32
32
  import { updateAssociations, updateModelByValues } from './update-associations';
33
33
  import { UpdateGuard } from './update-guard';
34
34
  import { EagerLoadingTree } from './eager-loading/eager-loading-tree';
35
+ import { flatten } from 'flat';
35
36
 
36
37
  const debug = require('debug')('noco-database');
37
38
 
@@ -210,6 +211,11 @@ export interface AggregateOptions {
210
211
  distinct?: boolean;
211
212
  }
212
213
 
214
+ interface FirstOrCreateOptions extends Transactionable {
215
+ filterKeys: string[];
216
+ values?: Values;
217
+ }
218
+
213
219
  export class Repository<TModelAttributes extends {} = any, TCreationAttributes extends {} = TModelAttributes>
214
220
  implements IRepository
215
221
  {
@@ -223,6 +229,50 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
223
229
  this.model = collection.model;
224
230
  }
225
231
 
232
+ public static valuesToFilter(values: Values, filterKeys: Array<string>) {
233
+ const filterAnd = [];
234
+ const flattedValues = flatten(values);
235
+
236
+ const keyWithOutArrayIndex = (key) => {
237
+ const chunks = key.split('.');
238
+ return chunks
239
+ .filter((chunk) => {
240
+ return !Boolean(chunk.match(/\d+/));
241
+ })
242
+ .join('.');
243
+ };
244
+
245
+ for (const filterKey of filterKeys) {
246
+ let filterValue;
247
+
248
+ for (const flattedKey of Object.keys(flattedValues)) {
249
+ const flattedKeyWithoutIndex = keyWithOutArrayIndex(flattedKey);
250
+
251
+ if (flattedKeyWithoutIndex === filterKey) {
252
+ if (filterValue) {
253
+ if (Array.isArray(filterValue)) {
254
+ filterValue.push(flattedValues[flattedKey]);
255
+ } else {
256
+ filterValue = [filterValue, flattedValues[flattedKey]];
257
+ }
258
+ } else {
259
+ filterValue = flattedValues[flattedKey];
260
+ }
261
+ }
262
+ }
263
+
264
+ if (filterValue) {
265
+ filterAnd.push({
266
+ [filterKey]: filterValue,
267
+ });
268
+ }
269
+ }
270
+
271
+ return {
272
+ $and: filterAnd,
273
+ };
274
+ }
275
+
226
276
  /**
227
277
  * return count by filter
228
278
  */
@@ -367,6 +417,7 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
367
417
  rootAttributes: opts.attributes,
368
418
  includeOption: opts.include,
369
419
  rootOrder: opts.order,
420
+ db: this.database,
370
421
  });
371
422
 
372
423
  await eagerLoadingTree.load(
@@ -427,6 +478,39 @@ export class Repository<TModelAttributes extends {} = any, TCreationAttributes e
427
478
  return rows.length == 1 ? rows[0] : null;
428
479
  }
429
480
 
481
+ /**
482
+ * Get the first record matching the attributes or create it.
483
+ */
484
+ async firstOrCreate(options: FirstOrCreateOptions) {
485
+ const { filterKeys, values, transaction } = options;
486
+ const filter = Repository.valuesToFilter(values, filterKeys);
487
+
488
+ const instance = await this.findOne({ filter, transaction });
489
+
490
+ if (instance) {
491
+ return instance;
492
+ }
493
+
494
+ return this.create({ values, transaction });
495
+ }
496
+
497
+ async updateOrCreate(options: FirstOrCreateOptions) {
498
+ const { filterKeys, values, transaction } = options;
499
+ const filter = Repository.valuesToFilter(values, filterKeys);
500
+
501
+ const instance = await this.findOne({ filter, transaction });
502
+
503
+ if (instance) {
504
+ return await this.update({
505
+ filterByTk: instance.get(this.collection.model.primaryKeyAttribute),
506
+ values,
507
+ transaction,
508
+ });
509
+ }
510
+
511
+ return this.create({ values, transaction });
512
+ }
513
+
430
514
  /**
431
515
  * Save instance to database
432
516
  *
@@ -200,6 +200,7 @@ function isReverseAssociationPair(a: any, b: any) {
200
200
 
201
201
  return (
202
202
  sourceAssoc.source.name === targetAssoc.target.name &&
203
+ sourceAssoc.target.name === targetAssoc.source.name &&
203
204
  sourceAssoc.foreignKey === targetAssoc.foreignKey &&
204
205
  sourceAssoc.sourceKey === targetAssoc.targetKey
205
206
  );
@@ -339,12 +340,14 @@ export async function updateSingleAssociation(
339
340
  }
340
341
 
341
342
  const instance = await model[createAccessor](value, { context, transaction });
343
+
342
344
  await updateAssociations(instance, value, {
343
345
  ...options,
344
346
  transaction,
345
347
  associationContext: association,
346
348
  updateAssociationValues: keys,
347
349
  });
350
+
348
351
  model.setDataValue(key, instance);
349
352
  // @ts-ignore
350
353
  if (association.targetKey) {
@@ -3,6 +3,8 @@ const postgres = {
3
3
  varchar: 'string',
4
4
  text: 'text',
5
5
  char: 'string',
6
+ oid: 'string',
7
+ name: 'string',
6
8
 
7
9
  smallint: 'integer',
8
10
  integer: 'integer',
@@ -30,52 +30,84 @@ export class ViewFieldInference {
30
30
  schema: options.viewSchema,
31
31
  });
32
32
 
33
- // @ts-ignore
34
- return Object.fromEntries(
35
- Object.entries(columns).map(([name, column]) => {
36
- const usage = columnUsage[name];
37
-
38
- if (usage) {
39
- const collectionField = (() => {
40
- const tableName = `${usage.table_schema ? `${usage.table_schema}.` : ''}${usage.table_name}`;
41
- const collection = db.tableNameCollectionMap.get(tableName);
42
- if (!collection) return false;
43
-
44
- const fieldValue = Object.values(collection.model.rawAttributes).find(
45
- (field) => field.field === usage.column_name,
46
- );
47
-
48
- if (!fieldValue) {
49
- return false;
50
- }
51
-
52
- // @ts-ignore
53
- const fieldName = fieldValue?.fieldName;
54
-
55
- return collection.getField(fieldName);
56
- })();
57
-
58
- if (collectionField && collectionField.options.interface) {
59
- return [
60
- name,
61
- {
62
- name,
63
- type: collectionField.type,
64
- source: `${collectionField.collection.name}.${collectionField.name}`,
65
- },
66
- ];
33
+ const rawFields = [];
34
+ for (const [name, column] of Object.entries(columns)) {
35
+ const inferResult: any = { name };
36
+
37
+ const usage = columnUsage[name];
38
+
39
+ if (usage) {
40
+ const collection = db.tableNameCollectionMap.get(
41
+ `${usage.table_schema ? `${usage.table_schema}.` : ''}${usage.table_name}`,
42
+ );
43
+
44
+ const collectionField = (() => {
45
+ if (!collection) return false;
46
+
47
+ const fieldAttribute = Object.values(collection.model.rawAttributes).find(
48
+ (field) => field.field === usage.column_name,
49
+ );
50
+
51
+ if (!fieldAttribute) {
52
+ return false;
53
+ }
54
+
55
+ // @ts-ignore
56
+ const fieldName = fieldAttribute.fieldName;
57
+
58
+ return collection.getField(fieldName);
59
+ })();
60
+
61
+ const belongsToAssociationField = (() => {
62
+ if (!collection) return false;
63
+
64
+ const field = Object.values(collection.model.rawAttributes).find(
65
+ (field) => field.field === usage.column_name,
66
+ );
67
+
68
+ if (!field) {
69
+ return false;
70
+ }
71
+
72
+ const association = Object.values(collection.model.associations).find(
73
+ (association) =>
74
+ association.associationType === 'BelongsTo' && association.foreignKey === (field as any).fieldName,
75
+ );
76
+
77
+ if (!association) {
78
+ return false;
79
+ }
80
+
81
+ return collection.getField(association.as);
82
+ })();
83
+
84
+ if (belongsToAssociationField) {
85
+ rawFields.push([
86
+ belongsToAssociationField.name,
87
+ {
88
+ name: belongsToAssociationField.name,
89
+ type: belongsToAssociationField.type,
90
+ source: `${belongsToAssociationField.collection.name}.${belongsToAssociationField.name}`,
91
+ },
92
+ ]);
93
+ }
94
+
95
+ if (collectionField) {
96
+ if (collectionField.options.interface) {
97
+ inferResult.type = collectionField.type;
98
+ inferResult.source = `${collectionField.collection.name}.${collectionField.name}`;
67
99
  }
68
100
  }
101
+ }
102
+
103
+ if (!inferResult.type) {
104
+ Object.assign(inferResult, this.inferToFieldType({ db, name, type: column.type }));
105
+ }
106
+
107
+ rawFields.push([name, inferResult]);
108
+ }
69
109
 
70
- return [
71
- name,
72
- {
73
- name,
74
- ...this.inferToFieldType({ db, name, type: column.type }),
75
- },
76
- ];
77
- }),
78
- );
110
+ return Object.fromEntries(rawFields);
79
111
  }
80
112
 
81
113
  static inferToFieldType(options: { db: Database; name: string; type: string }) {
@@ -4,7 +4,6 @@ export class ViewCollection extends Collection {
4
4
  constructor(options: CollectionOptions, context: CollectionContext) {
5
5
  options.autoGenId = false;
6
6
  options.timestamps = false;
7
- options.underscored = false;
8
7
 
9
8
  super(options, context);
10
9
  }