@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.
- package/lib/database.js +2 -1
- package/lib/eager-loading/eager-loading-tree.d.ts +4 -0
- package/lib/eager-loading/eager-loading-tree.js +79 -16
- package/lib/fields/belongs-to-field.d.ts +1 -1
- package/lib/fields/belongs-to-field.js +3 -2
- package/lib/fields/belongs-to-many-field.js +1 -1
- package/lib/fields/has-many-field.js +1 -1
- package/lib/fields/has-one-field.js +1 -1
- package/lib/filter-parser.js +15 -9
- package/lib/listeners/append-child-collection-name-after-repository-find.js +5 -1
- package/lib/options-parser.d.ts +1 -1
- package/lib/options-parser.js +15 -11
- package/lib/relation-repository/multiple-relation-repository.js +2 -1
- package/lib/relation-repository/single-relation-repository.js +2 -1
- package/lib/repository.d.ts +12 -0
- package/lib/repository.js +166 -66
- package/lib/update-associations.js +1 -1
- package/lib/view/field-type-map.d.ts +2 -0
- package/lib/view/field-type-map.js +2 -0
- package/lib/view/view-inference.js +52 -26
- package/lib/view-collection.js +0 -1
- package/package.json +4 -4
- package/src/__tests__/eager-loading/eager-loading-tree.test.ts +149 -0
- package/src/__tests__/field-options/sort-by.test.ts +3 -1
- package/src/__tests__/filter.test.ts +54 -0
- package/src/__tests__/inhertits/collection-inherits.test.ts +165 -0
- package/src/__tests__/repository/create.test.ts +129 -6
- package/src/__tests__/repository/find.test.ts +11 -0
- package/src/__tests__/repository.test.ts +24 -0
- package/src/__tests__/update-associations.test.ts +109 -1
- package/src/__tests__/view/view-inference.test.ts +1 -0
- package/src/database.ts +4 -1
- package/src/eager-loading/eager-loading-tree.ts +92 -17
- package/src/fields/belongs-to-field.ts +6 -4
- package/src/fields/belongs-to-many-field.ts +2 -1
- package/src/fields/has-many-field.ts +1 -1
- package/src/fields/has-one-field.ts +1 -1
- package/src/filter-parser.ts +17 -9
- package/src/listeners/append-child-collection-name-after-repository-find.ts +9 -5
- package/src/options-parser.ts +25 -19
- package/src/relation-repository/multiple-relation-repository.ts +1 -0
- package/src/relation-repository/single-relation-repository.ts +1 -0
- package/src/repository.ts +84 -0
- package/src/update-associations.ts +3 -0
- package/src/view/field-type-map.ts +2 -0
- package/src/view/view-inference.ts +75 -43
- 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) {
|
|
@@ -30,52 +30,84 @@ export class ViewFieldInference {
|
|
|
30
30
|
schema: options.viewSchema,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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 }) {
|
package/src/view-collection.ts
CHANGED