@rws-framework/db 3.12.5 → 4.1.0
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/dist/models/core/RWSModel.d.ts +14 -0
- package/dist/models/core/RWSModel.js +224 -3
- package/dist/models/interfaces/OpModelType.d.ts +1 -0
- package/dist/models/utils/FindUtils.js +20 -4
- package/dist/models/utils/HydrateUtils.js +13 -0
- package/dist/services/DBService.d.ts +2 -2
- package/dist/services/DBService.js +26 -2
- package/package.json +2 -1
- package/src/models/core/RWSModel.ts +257 -4
- package/src/models/interfaces/OpModelType.ts +4 -0
- package/src/models/utils/FindUtils.ts +30 -7
- package/src/models/utils/HydrateUtils.ts +16 -0
- package/src/services/DBService.ts +28 -3
- package/tsconfig.json +2 -1
|
@@ -15,6 +15,7 @@ declare class RWSModel<T> implements IModel {
|
|
|
15
15
|
static _BANNED_KEYS: string[];
|
|
16
16
|
static allModels: OpModelType<any>[];
|
|
17
17
|
static _CUT_KEYS: string[];
|
|
18
|
+
private _relationFields;
|
|
18
19
|
private postLoadExecuted;
|
|
19
20
|
constructor(data?: any);
|
|
20
21
|
isPostLoadExecuted(): boolean;
|
|
@@ -72,7 +73,20 @@ declare class RWSModel<T> implements IModel {
|
|
|
72
73
|
static count(where?: {
|
|
73
74
|
[k: string]: any;
|
|
74
75
|
}): Promise<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Build Prisma include object for relation preloading
|
|
78
|
+
*/
|
|
79
|
+
static buildPrismaIncludes<T extends RWSModel<T>>(this: OpModelType<T>, fields?: string[]): Promise<any>;
|
|
80
|
+
/**
|
|
81
|
+
* Check if relations are already populated from Prisma includes
|
|
82
|
+
*/
|
|
83
|
+
private checkRelationsPrePopulated;
|
|
84
|
+
/**
|
|
85
|
+
* Hydrate pre-populated relations from Prisma includes (one level only)
|
|
86
|
+
*/
|
|
87
|
+
private hydratePrePopulatedRelations;
|
|
75
88
|
static getDb(): DBService;
|
|
76
89
|
reload(): Promise<RWSModel<T> | null>;
|
|
90
|
+
getPropertyValue(key: string): any;
|
|
77
91
|
}
|
|
78
92
|
export { RWSModel };
|
|
@@ -27,6 +27,8 @@ class RWSModel {
|
|
|
27
27
|
static _BANNED_KEYS = ['_collection'];
|
|
28
28
|
static allModels = [];
|
|
29
29
|
static _CUT_KEYS = [];
|
|
30
|
+
// Store relation foreign key fields for reload() functionality
|
|
31
|
+
_relationFields = {};
|
|
30
32
|
postLoadExecuted = false;
|
|
31
33
|
constructor(data = null) {
|
|
32
34
|
if (!this.getCollection()) {
|
|
@@ -107,10 +109,26 @@ class RWSModel {
|
|
|
107
109
|
collections_to_models[model.getCollection()] = model;
|
|
108
110
|
});
|
|
109
111
|
const seriesHydrationfields = [];
|
|
110
|
-
if
|
|
112
|
+
// Check if relations are already populated from Prisma includes
|
|
113
|
+
const relationsAlreadyPopulated = this.checkRelationsPrePopulated(data, relOneData, relManyData);
|
|
114
|
+
if (allowRelations && !relationsAlreadyPopulated) {
|
|
115
|
+
// Use traditional relation hydration if not pre-populated
|
|
111
116
|
await HydrateUtils_1.HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
112
117
|
}
|
|
113
|
-
|
|
118
|
+
else if (allowRelations && relationsAlreadyPopulated) {
|
|
119
|
+
// Relations are already populated from Prisma, just assign them directly
|
|
120
|
+
await this.hydratePrePopulatedRelations(data, relOneData, relManyData);
|
|
121
|
+
// Create a copy of data without relation fields to prevent overwriting hydrated relations
|
|
122
|
+
const dataWithoutRelations = { ...data };
|
|
123
|
+
for (const key in relOneData) {
|
|
124
|
+
delete dataWithoutRelations[key];
|
|
125
|
+
}
|
|
126
|
+
for (const key in relManyData) {
|
|
127
|
+
delete dataWithoutRelations[key];
|
|
128
|
+
}
|
|
129
|
+
data = dataWithoutRelations;
|
|
130
|
+
}
|
|
131
|
+
// Process regular fields and time series (excluding relations when pre-populated)
|
|
114
132
|
await HydrateUtils_1.HydrateUtils.hydrateDataFields(this, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
115
133
|
if (!this.isPostLoadExecuted() && postLoadExecute) {
|
|
116
134
|
await this.postLoad();
|
|
@@ -311,6 +329,187 @@ class RWSModel {
|
|
|
311
329
|
static async count(where = {}) {
|
|
312
330
|
return await this.services.dbService.count(this, where);
|
|
313
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Build Prisma include object for relation preloading
|
|
334
|
+
*/
|
|
335
|
+
static async buildPrismaIncludes(fields) {
|
|
336
|
+
const tempInstance = new this();
|
|
337
|
+
const classFields = FieldsHelper_1.FieldsHelper.getAllClassFields(this);
|
|
338
|
+
const [relOneData, relManyData] = await Promise.all([
|
|
339
|
+
this.getRelationOneMeta(tempInstance, classFields),
|
|
340
|
+
this.getRelationManyMeta(tempInstance, classFields)
|
|
341
|
+
]);
|
|
342
|
+
// Get relations configuration from @RWSCollection decorator
|
|
343
|
+
const allowedRelations = this._RELATIONS || {};
|
|
344
|
+
const hasRelationsConfig = Object.keys(allowedRelations).length > 0;
|
|
345
|
+
const includes = {};
|
|
346
|
+
// Helper function to determine if a relation should be included
|
|
347
|
+
const shouldIncludeRelation = (relationName) => {
|
|
348
|
+
// If relations config exists, only include relations that are explicitly enabled
|
|
349
|
+
if (hasRelationsConfig) {
|
|
350
|
+
if (!allowedRelations[relationName]) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// If fields are specified, only include if relation is in fields array
|
|
355
|
+
if (fields && fields.length > 0) {
|
|
356
|
+
return fields.includes(relationName);
|
|
357
|
+
}
|
|
358
|
+
// If no fields specified but relations config exists, include enabled relations
|
|
359
|
+
if (hasRelationsConfig) {
|
|
360
|
+
return allowedRelations[relationName] === true;
|
|
361
|
+
}
|
|
362
|
+
// If no relations config and no fields specified, include all relations
|
|
363
|
+
return true;
|
|
364
|
+
};
|
|
365
|
+
// Add one-to-one and many-to-one relations
|
|
366
|
+
for (const key in relOneData) {
|
|
367
|
+
if (shouldIncludeRelation(key)) {
|
|
368
|
+
includes[key] = true;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Add one-to-many relations
|
|
372
|
+
for (const key in relManyData) {
|
|
373
|
+
if (shouldIncludeRelation(key)) {
|
|
374
|
+
includes[key] = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return Object.keys(includes).length > 0 ? includes : null;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Check if relations are already populated from Prisma includes
|
|
381
|
+
*/
|
|
382
|
+
checkRelationsPrePopulated(data, relOneData, relManyData) {
|
|
383
|
+
// Check if any relation key in data contains object data instead of just ID
|
|
384
|
+
for (const key in relOneData) {
|
|
385
|
+
if (data[key] && typeof data[key] === 'object' && data[key] !== null) {
|
|
386
|
+
// For one-to-one relations, check if it has an id or if it's a full object
|
|
387
|
+
if (data[key].id || Object.keys(data[key]).length > 1) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const key in relManyData) {
|
|
393
|
+
const relationValue = data[key];
|
|
394
|
+
const relMeta = relManyData[key];
|
|
395
|
+
if (relMeta.singular) {
|
|
396
|
+
// Singular inverse relation - should be a single object
|
|
397
|
+
if (relationValue && typeof relationValue === 'object' && relationValue !== null &&
|
|
398
|
+
(relationValue.id || Object.keys(relationValue).length > 1)) {
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
else if (relationValue && Array.isArray(relationValue) && relationValue.length > 0) {
|
|
403
|
+
// Regular one-to-many relations - should be arrays
|
|
404
|
+
if (typeof relationValue[0] === 'object' && relationValue[0] !== null) {
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Hydrate pre-populated relations from Prisma includes (one level only)
|
|
413
|
+
*/
|
|
414
|
+
async hydratePrePopulatedRelations(data, relOneData, relManyData) {
|
|
415
|
+
// Handle one-to-one and many-to-one relations
|
|
416
|
+
for (const key in relOneData) {
|
|
417
|
+
if (data[key] && typeof data[key] === 'object' && data[key] !== null) {
|
|
418
|
+
const relationData = data[key];
|
|
419
|
+
const relMeta = relOneData[key];
|
|
420
|
+
const ModelClass = relMeta.model; // Use the model class directly from metadata
|
|
421
|
+
if (ModelClass) {
|
|
422
|
+
// Check if it's already a full object with data or just an ID reference
|
|
423
|
+
if (relationData.id || Object.keys(relationData).length > 1) {
|
|
424
|
+
// Create new instance and hydrate ONLY basic fields, NO RELATIONS
|
|
425
|
+
const relatedInstance = new ModelClass();
|
|
426
|
+
await relatedInstance._asyncFill(relationData, false, false, true);
|
|
427
|
+
this[key] = relatedInstance;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Handle one-to-many relations
|
|
433
|
+
for (const key in relManyData) {
|
|
434
|
+
if (data[key]) {
|
|
435
|
+
const relationData = data[key];
|
|
436
|
+
const relMeta = relManyData[key];
|
|
437
|
+
const ModelClass = relMeta.inversionModel; // Use the model class directly from metadata
|
|
438
|
+
if (ModelClass) {
|
|
439
|
+
// Check if this is a singular inverse relation
|
|
440
|
+
if (relMeta.singular && !Array.isArray(relationData)) {
|
|
441
|
+
// Handle singular inverse relation as a single object
|
|
442
|
+
if (typeof relationData === 'object' && relationData !== null &&
|
|
443
|
+
(relationData.id || Object.keys(relationData).length > 1)) {
|
|
444
|
+
const relatedInstance = new ModelClass();
|
|
445
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
446
|
+
const tempInstance = new ModelClass();
|
|
447
|
+
const childClassFields = FieldsHelper_1.FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
448
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
449
|
+
RelationUtils_1.RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
450
|
+
RelationUtils_1.RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
451
|
+
]);
|
|
452
|
+
// Store foreign key fields from relation metadata
|
|
453
|
+
for (const relKey in childRelOneData) {
|
|
454
|
+
const relMeta = childRelOneData[relKey];
|
|
455
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
456
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
457
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
458
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
for (const relKey in childRelManyData) {
|
|
462
|
+
const relMeta = childRelManyData[relKey];
|
|
463
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
464
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
465
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
466
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
await relatedInstance._asyncFill(relationData, false, false, true);
|
|
470
|
+
this[key] = relatedInstance;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
else if (Array.isArray(relationData) && relationData.length > 0) {
|
|
474
|
+
// Handle regular one-to-many relations as arrays
|
|
475
|
+
const relatedInstances = [];
|
|
476
|
+
for (const itemData of relationData) {
|
|
477
|
+
if (typeof itemData === 'object' && itemData !== null) {
|
|
478
|
+
const relatedInstance = new ModelClass();
|
|
479
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
480
|
+
const tempInstance = new ModelClass();
|
|
481
|
+
const childClassFields = FieldsHelper_1.FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
482
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
483
|
+
RelationUtils_1.RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
484
|
+
RelationUtils_1.RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
485
|
+
]);
|
|
486
|
+
// Store foreign key fields from relation metadata
|
|
487
|
+
for (const relKey in childRelOneData) {
|
|
488
|
+
const relMeta = childRelOneData[relKey];
|
|
489
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
490
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
491
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
492
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
for (const relKey in childRelManyData) {
|
|
496
|
+
const relMeta = childRelManyData[relKey];
|
|
497
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
498
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
499
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
500
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
await relatedInstance._asyncFill(itemData, false, false, true);
|
|
504
|
+
relatedInstances.push(relatedInstance);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
this[key] = relatedInstances;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
314
513
|
static getDb() {
|
|
315
514
|
return this.services.dbService;
|
|
316
515
|
}
|
|
@@ -325,7 +524,29 @@ class RWSModel {
|
|
|
325
524
|
else {
|
|
326
525
|
where[pk] = this[pk];
|
|
327
526
|
}
|
|
328
|
-
|
|
527
|
+
// Find the fresh data from database
|
|
528
|
+
const freshData = await FindUtils_1.FindUtils.findOneBy(this.constructor, { conditions: where });
|
|
529
|
+
if (!freshData) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
// Convert the fresh instance back to plain data for hydration
|
|
533
|
+
const plainData = await freshData.toMongo();
|
|
534
|
+
// Preserve foreign key fields from _relationFields to ensure relations can be hydrated
|
|
535
|
+
for (const key in this._relationFields) {
|
|
536
|
+
if (plainData[key] === undefined) {
|
|
537
|
+
plainData[key] = this._relationFields[key];
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Hydrate current instance with fresh data including relations
|
|
541
|
+
await this._asyncFill(plainData, true, true, true);
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
// Helper method to get property with fallback to stored relation fields
|
|
545
|
+
getPropertyValue(key) {
|
|
546
|
+
if (this.hasOwnProperty(key) || this[key] !== undefined) {
|
|
547
|
+
return this[key];
|
|
548
|
+
}
|
|
549
|
+
return this._relationFields[key];
|
|
329
550
|
}
|
|
330
551
|
}
|
|
331
552
|
exports.RWSModel = RWSModel;
|
|
@@ -35,6 +35,7 @@ export interface OpModelType<T> {
|
|
|
35
35
|
count(where?: {
|
|
36
36
|
[k: string]: any;
|
|
37
37
|
}): Promise<number>;
|
|
38
|
+
buildPrismaIncludes<T extends RWSModel<T>>(this: OpModelType<T>, fields?: string[]): Promise<any>;
|
|
38
39
|
isSubclass<T extends RWSModel<T>, C extends new () => T>(constructor: C, baseClass: new () => T): boolean;
|
|
39
40
|
getModelAnnotations<T extends unknown>(constructor: new () => T): Promise<Record<string, {
|
|
40
41
|
annotationType: string;
|
|
@@ -10,7 +10,11 @@ class FindUtils {
|
|
|
10
10
|
const fullData = findParams?.fullData ?? false;
|
|
11
11
|
opModel.checkForInclusionWithThrow('');
|
|
12
12
|
const collection = Reflect.get(opModel, '_collection');
|
|
13
|
-
|
|
13
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
14
|
+
const prismaOptions = allowRelations ? {
|
|
15
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
16
|
+
} : null;
|
|
17
|
+
const dbData = await opModel.services.dbService.findOneBy(collection, conditions, fields, ordering, prismaOptions);
|
|
14
18
|
if (dbData) {
|
|
15
19
|
const inst = new opModel();
|
|
16
20
|
const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
|
|
@@ -25,7 +29,11 @@ class FindUtils {
|
|
|
25
29
|
const fullData = findParams?.fullData ?? false;
|
|
26
30
|
const collection = Reflect.get(opModel, '_collection');
|
|
27
31
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
28
|
-
|
|
32
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
33
|
+
const prismaOptions = allowRelations ? {
|
|
34
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
35
|
+
} : null;
|
|
36
|
+
const dbData = await opModel.services.dbService.findOneBy(collection, { id }, fields, ordering, prismaOptions);
|
|
29
37
|
if (dbData) {
|
|
30
38
|
const inst = new opModel();
|
|
31
39
|
const loaded = await inst._asyncFill(dbData, fullData, allowRelations, findParams?.cancelPostLoad ? false : true);
|
|
@@ -43,7 +51,11 @@ class FindUtils {
|
|
|
43
51
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
44
52
|
try {
|
|
45
53
|
const paginateParams = findParams?.pagination ? findParams?.pagination : undefined;
|
|
46
|
-
|
|
54
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
55
|
+
const prismaOptions = allowRelations ? {
|
|
56
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
57
|
+
} : null;
|
|
58
|
+
const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams, prismaOptions);
|
|
47
59
|
if (dbData.length) {
|
|
48
60
|
const instanced = [];
|
|
49
61
|
for (const data of dbData) {
|
|
@@ -68,7 +80,11 @@ class FindUtils {
|
|
|
68
80
|
const collection = Reflect.get(opModel, '_collection');
|
|
69
81
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
70
82
|
try {
|
|
71
|
-
|
|
83
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
84
|
+
const prismaOptions = allowRelations ? {
|
|
85
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
86
|
+
} : null;
|
|
87
|
+
const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams, prismaOptions);
|
|
72
88
|
if (dbData.length) {
|
|
73
89
|
const instanced = [];
|
|
74
90
|
for (const data of dbData) {
|
|
@@ -11,14 +11,27 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
11
11
|
class HydrateUtils {
|
|
12
12
|
static async hydrateDataFields(model, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data) {
|
|
13
13
|
const timeSeriesIds = TimeSeriesUtils_1.TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
14
|
+
// Build a set of foreign key field names to skip
|
|
15
|
+
const foreignKeyFields = new Set();
|
|
16
|
+
for (const relationName in relOneData) {
|
|
17
|
+
const relationMeta = relOneData[relationName];
|
|
18
|
+
if (relationMeta.hydrationField) {
|
|
19
|
+
foreignKeyFields.add(relationMeta.hydrationField);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
14
22
|
for (const key in data) {
|
|
15
23
|
if (data.hasOwnProperty(key)) {
|
|
16
24
|
if (!fullDataMode && (model).constructor._CUT_KEYS.includes(key)) {
|
|
17
25
|
continue;
|
|
18
26
|
}
|
|
27
|
+
// Skip relation property names
|
|
19
28
|
if (Object.keys(relOneData).includes(key)) {
|
|
20
29
|
continue;
|
|
21
30
|
}
|
|
31
|
+
// Skip foreign key field names
|
|
32
|
+
if (foreignKeyFields.has(key)) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
22
35
|
if (seriesHydrationfields.includes(key)) {
|
|
23
36
|
continue;
|
|
24
37
|
}
|
|
@@ -24,9 +24,9 @@ declare class DBService {
|
|
|
24
24
|
watchCollection(collectionName: string, preRun: () => void): Promise<any>;
|
|
25
25
|
insert(data: any, collection: string, isTimeSeries?: boolean): Promise<any>;
|
|
26
26
|
update(data: any, collection: string, pk: string | string[]): Promise<IModel>;
|
|
27
|
-
findOneBy(collection: string, conditions: any, fields?: string[] | null, ordering?: OrderByType): Promise<IModel | null>;
|
|
27
|
+
findOneBy(collection: string, conditions: any, fields?: string[] | null, ordering?: OrderByType, prismaOptions?: any): Promise<IModel | null>;
|
|
28
28
|
delete(collection: string, conditions: any): Promise<void>;
|
|
29
|
-
findBy(collection: string, conditions: any, fields?: string[] | null, ordering?: OrderByType, pagination?: IPaginationParams): Promise<IModel[]>;
|
|
29
|
+
findBy(collection: string, conditions: any, fields?: string[] | null, ordering?: OrderByType, pagination?: IPaginationParams, prismaOptions?: any): Promise<IModel[]>;
|
|
30
30
|
collectionExists(collection_name: string): Promise<boolean>;
|
|
31
31
|
createTimeSeriesCollection(collection_name: string): Promise<Collection<ITimeSeries>>;
|
|
32
32
|
private getCollectionHandler;
|
|
@@ -126,13 +126,25 @@ class DBService {
|
|
|
126
126
|
});
|
|
127
127
|
return await this.findOneBy(collection, where);
|
|
128
128
|
}
|
|
129
|
-
async findOneBy(collection, conditions, fields = null, ordering = null) {
|
|
129
|
+
async findOneBy(collection, conditions, fields = null, ordering = null, prismaOptions = null) {
|
|
130
130
|
const params = { where: conditions };
|
|
131
131
|
if (fields) {
|
|
132
132
|
params.select = {};
|
|
133
133
|
fields.forEach((fieldName) => {
|
|
134
134
|
params.select[fieldName] = true;
|
|
135
135
|
});
|
|
136
|
+
// Add relation fields to select instead of using include when fields are specified
|
|
137
|
+
if (prismaOptions?.include) {
|
|
138
|
+
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
139
|
+
if (fields.includes(relationField)) {
|
|
140
|
+
params.select[relationField] = true;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else if (prismaOptions?.include) {
|
|
146
|
+
// Only use include when no fields are specified
|
|
147
|
+
params.include = prismaOptions.include;
|
|
136
148
|
}
|
|
137
149
|
if (ordering) {
|
|
138
150
|
params.orderBy = this.convertOrderingToPrismaFormat(ordering);
|
|
@@ -144,13 +156,25 @@ class DBService {
|
|
|
144
156
|
await this.getCollectionHandler(collection).deleteMany({ where: conditions });
|
|
145
157
|
return;
|
|
146
158
|
}
|
|
147
|
-
async findBy(collection, conditions, fields = null, ordering = null, pagination = null) {
|
|
159
|
+
async findBy(collection, conditions, fields = null, ordering = null, pagination = null, prismaOptions = null) {
|
|
148
160
|
const params = { where: conditions };
|
|
149
161
|
if (fields) {
|
|
150
162
|
params.select = {};
|
|
151
163
|
fields.forEach((fieldName) => {
|
|
152
164
|
params.select[fieldName] = true;
|
|
153
165
|
});
|
|
166
|
+
// Add relation fields to select instead of using include when fields are specified
|
|
167
|
+
if (prismaOptions?.include) {
|
|
168
|
+
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
169
|
+
if (fields.includes(relationField)) {
|
|
170
|
+
params.select[relationField] = true;
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
else if (prismaOptions?.include) {
|
|
176
|
+
// Only use include when no fields are specified
|
|
177
|
+
params.include = prismaOptions.include;
|
|
154
178
|
}
|
|
155
179
|
if (ordering) {
|
|
156
180
|
params.orderBy = this.convertOrderingToPrismaFormat(ordering);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rws-framework/db",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.1.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"tsconfig-paths-webpack-plugin": "^4.1.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
27
28
|
"@types/xml2js": "^0.4.14",
|
|
28
29
|
"typescript": "^5.7.2"
|
|
29
30
|
},
|
|
@@ -27,6 +27,9 @@ class RWSModel<T> implements IModel {
|
|
|
27
27
|
static _BANNED_KEYS = ['_collection'];
|
|
28
28
|
static allModels: OpModelType<any>[] = [];
|
|
29
29
|
static _CUT_KEYS: string[] = [];
|
|
30
|
+
|
|
31
|
+
// Store relation foreign key fields for reload() functionality
|
|
32
|
+
private _relationFields: Record<string, any> = {};
|
|
30
33
|
|
|
31
34
|
private postLoadExecuted: boolean = false;
|
|
32
35
|
|
|
@@ -129,12 +132,28 @@ class RWSModel<T> implements IModel {
|
|
|
129
132
|
|
|
130
133
|
const seriesHydrationfields: string[] = [];
|
|
131
134
|
|
|
135
|
+
// Check if relations are already populated from Prisma includes
|
|
136
|
+
const relationsAlreadyPopulated = this.checkRelationsPrePopulated(data, relOneData, relManyData);
|
|
132
137
|
|
|
133
|
-
if (allowRelations) {
|
|
138
|
+
if (allowRelations && !relationsAlreadyPopulated) {
|
|
139
|
+
// Use traditional relation hydration if not pre-populated
|
|
134
140
|
await HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
141
|
+
} else if (allowRelations && relationsAlreadyPopulated) {
|
|
142
|
+
// Relations are already populated from Prisma, just assign them directly
|
|
143
|
+
await this.hydratePrePopulatedRelations(data, relOneData, relManyData);
|
|
144
|
+
|
|
145
|
+
// Create a copy of data without relation fields to prevent overwriting hydrated relations
|
|
146
|
+
const dataWithoutRelations = { ...data };
|
|
147
|
+
for (const key in relOneData) {
|
|
148
|
+
delete dataWithoutRelations[key];
|
|
149
|
+
}
|
|
150
|
+
for (const key in relManyData) {
|
|
151
|
+
delete dataWithoutRelations[key];
|
|
152
|
+
}
|
|
153
|
+
data = dataWithoutRelations;
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
// Process regular fields and time series
|
|
156
|
+
// Process regular fields and time series (excluding relations when pre-populated)
|
|
138
157
|
await HydrateUtils.hydrateDataFields(this, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
139
158
|
|
|
140
159
|
if(!this.isPostLoadExecuted() && postLoadExecute){
|
|
@@ -416,10 +435,216 @@ class RWSModel<T> implements IModel {
|
|
|
416
435
|
return this.services.dbService;
|
|
417
436
|
}
|
|
418
437
|
|
|
419
|
-
public static async count(where: {[k: string]: any} = {}): Promise<number>{
|
|
438
|
+
public static async count(where: {[k: string]: any} = {}): Promise<number>{
|
|
420
439
|
return await this.services.dbService.count(this as OpModelType<any>, where);
|
|
421
440
|
}
|
|
422
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Build Prisma include object for relation preloading
|
|
444
|
+
*/
|
|
445
|
+
public static async buildPrismaIncludes<T extends RWSModel<T>>(this: OpModelType<T>, fields?: string[]): Promise<any> {
|
|
446
|
+
const tempInstance = new this();
|
|
447
|
+
const classFields = FieldsHelper.getAllClassFields(this);
|
|
448
|
+
|
|
449
|
+
const [relOneData, relManyData] = await Promise.all([
|
|
450
|
+
this.getRelationOneMeta(tempInstance, classFields),
|
|
451
|
+
this.getRelationManyMeta(tempInstance, classFields)
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
// Get relations configuration from @RWSCollection decorator
|
|
455
|
+
const allowedRelations = this._RELATIONS || {};
|
|
456
|
+
const hasRelationsConfig = Object.keys(allowedRelations).length > 0;
|
|
457
|
+
|
|
458
|
+
const includes: any = {};
|
|
459
|
+
|
|
460
|
+
// Helper function to determine if a relation should be included
|
|
461
|
+
const shouldIncludeRelation = (relationName: string): boolean => {
|
|
462
|
+
// If relations config exists, only include relations that are explicitly enabled
|
|
463
|
+
if (hasRelationsConfig) {
|
|
464
|
+
if (!allowedRelations[relationName]) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// If fields are specified, only include if relation is in fields array
|
|
470
|
+
if (fields && fields.length > 0) {
|
|
471
|
+
return fields.includes(relationName);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// If no fields specified but relations config exists, include enabled relations
|
|
475
|
+
if (hasRelationsConfig) {
|
|
476
|
+
return allowedRelations[relationName] === true;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// If no relations config and no fields specified, include all relations
|
|
480
|
+
return true;
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Add one-to-one and many-to-one relations
|
|
484
|
+
for (const key in relOneData) {
|
|
485
|
+
if (shouldIncludeRelation(key)) {
|
|
486
|
+
includes[key] = true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Add one-to-many relations
|
|
491
|
+
for (const key in relManyData) {
|
|
492
|
+
if (shouldIncludeRelation(key)) {
|
|
493
|
+
includes[key] = true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return Object.keys(includes).length > 0 ? includes : null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check if relations are already populated from Prisma includes
|
|
502
|
+
*/
|
|
503
|
+
private checkRelationsPrePopulated(data: any, relOneData: any, relManyData: any): boolean {
|
|
504
|
+
// Check if any relation key in data contains object data instead of just ID
|
|
505
|
+
for (const key in relOneData) {
|
|
506
|
+
if (data[key] && typeof data[key] === 'object' && data[key] !== null) {
|
|
507
|
+
// For one-to-one relations, check if it has an id or if it's a full object
|
|
508
|
+
if (data[key].id || Object.keys(data[key]).length > 1) {
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
for (const key in relManyData) {
|
|
515
|
+
const relationValue = data[key];
|
|
516
|
+
const relMeta = relManyData[key];
|
|
517
|
+
|
|
518
|
+
if (relMeta.singular) {
|
|
519
|
+
// Singular inverse relation - should be a single object
|
|
520
|
+
if (relationValue && typeof relationValue === 'object' && relationValue !== null &&
|
|
521
|
+
(relationValue.id || Object.keys(relationValue).length > 1)) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
} else if (relationValue && Array.isArray(relationValue) && relationValue.length > 0) {
|
|
525
|
+
// Regular one-to-many relations - should be arrays
|
|
526
|
+
if (typeof relationValue[0] === 'object' && relationValue[0] !== null) {
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Hydrate pre-populated relations from Prisma includes (one level only)
|
|
537
|
+
*/
|
|
538
|
+
private async hydratePrePopulatedRelations(data: any, relOneData: any, relManyData: any): Promise<void> {
|
|
539
|
+
// Handle one-to-one and many-to-one relations
|
|
540
|
+
for (const key in relOneData) {
|
|
541
|
+
if (data[key] && typeof data[key] === 'object' && data[key] !== null) {
|
|
542
|
+
const relationData = data[key];
|
|
543
|
+
const relMeta = relOneData[key];
|
|
544
|
+
const ModelClass = relMeta.model; // Use the model class directly from metadata
|
|
545
|
+
|
|
546
|
+
if (ModelClass) {
|
|
547
|
+
// Check if it's already a full object with data or just an ID reference
|
|
548
|
+
if (relationData.id || Object.keys(relationData).length > 1) {
|
|
549
|
+
// Create new instance and hydrate ONLY basic fields, NO RELATIONS
|
|
550
|
+
const relatedInstance = new ModelClass();
|
|
551
|
+
await relatedInstance._asyncFill(relationData, false, false, true);
|
|
552
|
+
this[key] = relatedInstance;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Handle one-to-many relations
|
|
559
|
+
for (const key in relManyData) {
|
|
560
|
+
if (data[key]) {
|
|
561
|
+
const relationData = data[key];
|
|
562
|
+
const relMeta = relManyData[key];
|
|
563
|
+
const ModelClass = relMeta.inversionModel; // Use the model class directly from metadata
|
|
564
|
+
|
|
565
|
+
if (ModelClass) {
|
|
566
|
+
// Check if this is a singular inverse relation
|
|
567
|
+
if (relMeta.singular && !Array.isArray(relationData)) {
|
|
568
|
+
// Handle singular inverse relation as a single object
|
|
569
|
+
if (typeof relationData === 'object' && relationData !== null &&
|
|
570
|
+
(relationData.id || Object.keys(relationData).length > 1)) {
|
|
571
|
+
const relatedInstance = new ModelClass();
|
|
572
|
+
|
|
573
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
574
|
+
const tempInstance = new ModelClass();
|
|
575
|
+
const childClassFields = FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
576
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
577
|
+
RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
578
|
+
RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
579
|
+
]);
|
|
580
|
+
|
|
581
|
+
// Store foreign key fields from relation metadata
|
|
582
|
+
for (const relKey in childRelOneData) {
|
|
583
|
+
const relMeta = childRelOneData[relKey];
|
|
584
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
585
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
586
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
587
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
for (const relKey in childRelManyData) {
|
|
592
|
+
const relMeta = childRelManyData[relKey];
|
|
593
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
594
|
+
if (foreignKeyField && relationData[foreignKeyField] !== undefined) {
|
|
595
|
+
relatedInstance._relationFields[foreignKeyField] = relationData[foreignKeyField];
|
|
596
|
+
relatedInstance[foreignKeyField] = relationData[foreignKeyField];
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
await relatedInstance._asyncFill(relationData, false, false, true);
|
|
601
|
+
this[key] = relatedInstance;
|
|
602
|
+
}
|
|
603
|
+
} else if (Array.isArray(relationData) && relationData.length > 0) {
|
|
604
|
+
// Handle regular one-to-many relations as arrays
|
|
605
|
+
const relatedInstances = [];
|
|
606
|
+
for (const itemData of relationData) {
|
|
607
|
+
if (typeof itemData === 'object' && itemData !== null) {
|
|
608
|
+
const relatedInstance = new ModelClass();
|
|
609
|
+
|
|
610
|
+
// Check relation metadata to identify foreign key fields that need to be preserved
|
|
611
|
+
const tempInstance = new ModelClass();
|
|
612
|
+
const childClassFields = FieldsHelper.getAllClassFields(tempInstance.constructor);
|
|
613
|
+
const [childRelOneData, childRelManyData] = await Promise.all([
|
|
614
|
+
RelationUtils.getRelationOneMeta(tempInstance, childClassFields),
|
|
615
|
+
RelationUtils.getRelationManyMeta(tempInstance, childClassFields)
|
|
616
|
+
]);
|
|
617
|
+
|
|
618
|
+
// Store foreign key fields from relation metadata
|
|
619
|
+
for (const relKey in childRelOneData) {
|
|
620
|
+
const relMeta = childRelOneData[relKey];
|
|
621
|
+
const foreignKeyField = relMeta.hydrationField; // e.g., 'avatar_id', 'knowledge_id'
|
|
622
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
623
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
624
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
for (const relKey in childRelManyData) {
|
|
629
|
+
const relMeta = childRelManyData[relKey];
|
|
630
|
+
const foreignKeyField = relMeta.foreignKey; // Use foreignKey for inverse relations
|
|
631
|
+
if (foreignKeyField && itemData[foreignKeyField] !== undefined) {
|
|
632
|
+
relatedInstance._relationFields[foreignKeyField] = itemData[foreignKeyField];
|
|
633
|
+
relatedInstance[foreignKeyField] = itemData[foreignKeyField];
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
await relatedInstance._asyncFill(itemData, false, false, true);
|
|
638
|
+
relatedInstances.push(relatedInstance);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
this[key] = relatedInstances;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
423
648
|
public static getDb(): DBService
|
|
424
649
|
{
|
|
425
650
|
return this.services.dbService;
|
|
@@ -438,7 +663,35 @@ class RWSModel<T> implements IModel {
|
|
|
438
663
|
where[pk as string] = this[pk as string]
|
|
439
664
|
}
|
|
440
665
|
|
|
441
|
-
|
|
666
|
+
// Find the fresh data from database
|
|
667
|
+
const freshData = await FindUtils.findOneBy(this.constructor as OpModelType<any>, { conditions: where });
|
|
668
|
+
|
|
669
|
+
if (!freshData) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Convert the fresh instance back to plain data for hydration
|
|
674
|
+
const plainData = await freshData.toMongo();
|
|
675
|
+
|
|
676
|
+
// Preserve foreign key fields from _relationFields to ensure relations can be hydrated
|
|
677
|
+
for (const key in this._relationFields) {
|
|
678
|
+
if (plainData[key] === undefined) {
|
|
679
|
+
plainData[key] = this._relationFields[key];
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Hydrate current instance with fresh data including relations
|
|
684
|
+
await this._asyncFill(plainData, true, true, true);
|
|
685
|
+
|
|
686
|
+
return this;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Helper method to get property with fallback to stored relation fields
|
|
690
|
+
getPropertyValue(key: string): any {
|
|
691
|
+
if (this.hasOwnProperty(key) || this[key] !== undefined) {
|
|
692
|
+
return this[key];
|
|
693
|
+
}
|
|
694
|
+
return this._relationFields[key];
|
|
442
695
|
}
|
|
443
696
|
}
|
|
444
697
|
|
|
@@ -63,6 +63,10 @@ export interface OpModelType<T> {
|
|
|
63
63
|
preRun: () => void
|
|
64
64
|
): Promise<any>;
|
|
65
65
|
count(where?: { [k: string]: any }): Promise<number>;
|
|
66
|
+
buildPrismaIncludes<T extends RWSModel<T>>(
|
|
67
|
+
this: OpModelType<T>,
|
|
68
|
+
fields?: string[]
|
|
69
|
+
): Promise<any>;
|
|
66
70
|
isSubclass<T extends RWSModel<T>, C extends new () => T>(
|
|
67
71
|
constructor: C,
|
|
68
72
|
baseClass: new () => T
|
|
@@ -20,10 +20,14 @@ export class FindUtils {
|
|
|
20
20
|
|
|
21
21
|
opModel.checkForInclusionWithThrow('');
|
|
22
22
|
|
|
23
|
-
|
|
24
23
|
const collection = Reflect.get(opModel, '_collection');
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
|
|
25
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
26
|
+
const prismaOptions = allowRelations ? {
|
|
27
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
28
|
+
} : null;
|
|
29
|
+
|
|
30
|
+
const dbData = await opModel.services.dbService.findOneBy(collection, conditions, fields, ordering, prismaOptions);
|
|
27
31
|
|
|
28
32
|
if (dbData) {
|
|
29
33
|
const inst: T = new (opModel as { new(): T })();
|
|
@@ -47,7 +51,13 @@ export class FindUtils {
|
|
|
47
51
|
const collection = Reflect.get(opModel, '_collection');
|
|
48
52
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
49
53
|
|
|
50
|
-
|
|
54
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
55
|
+
const prismaOptions = allowRelations ? {
|
|
56
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
57
|
+
} : null;
|
|
58
|
+
|
|
59
|
+
const dbData = await opModel.services.dbService.findOneBy(collection, { id }, fields, ordering, prismaOptions);
|
|
60
|
+
|
|
51
61
|
|
|
52
62
|
if (dbData) {
|
|
53
63
|
const inst: T = new (opModel as { new(): T })();
|
|
@@ -72,12 +82,18 @@ export class FindUtils {
|
|
|
72
82
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
73
83
|
try {
|
|
74
84
|
const paginateParams = findParams?.pagination ? findParams?.pagination : undefined;
|
|
75
|
-
|
|
85
|
+
|
|
86
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
87
|
+
const prismaOptions = allowRelations ? {
|
|
88
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
89
|
+
} : null;
|
|
90
|
+
|
|
91
|
+
const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams, prismaOptions);
|
|
76
92
|
|
|
77
93
|
if (dbData.length) {
|
|
78
94
|
const instanced: T[] = [];
|
|
79
95
|
|
|
80
|
-
for (const data of dbData) {
|
|
96
|
+
for (const data of dbData) {
|
|
81
97
|
const inst: T = new (opModel as { new(): T })();
|
|
82
98
|
|
|
83
99
|
instanced.push((await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true)) as T);
|
|
@@ -108,7 +124,13 @@ export class FindUtils {
|
|
|
108
124
|
const collection = Reflect.get(opModel, '_collection');
|
|
109
125
|
opModel.checkForInclusionWithThrow(opModel.name);
|
|
110
126
|
try {
|
|
111
|
-
|
|
127
|
+
// Build Prisma includes for relation preloading if relations are allowed
|
|
128
|
+
const prismaOptions = allowRelations ? {
|
|
129
|
+
include: await opModel.buildPrismaIncludes(fields)
|
|
130
|
+
} : null;
|
|
131
|
+
|
|
132
|
+
const dbData = await opModel.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams, prismaOptions);
|
|
133
|
+
|
|
112
134
|
if (dbData.length) {
|
|
113
135
|
const instanced: T[] = [];
|
|
114
136
|
|
|
@@ -116,6 +138,7 @@ export class FindUtils {
|
|
|
116
138
|
const inst: T = new (opModel as { new(): T })();
|
|
117
139
|
instanced.push((await inst._asyncFill(data, fullData, allowRelations, findParams?.cancelPostLoad ? false : true)) as T);
|
|
118
140
|
}
|
|
141
|
+
|
|
119
142
|
|
|
120
143
|
return instanced;
|
|
121
144
|
}
|
|
@@ -10,16 +10,32 @@ import chalk from 'chalk';
|
|
|
10
10
|
export class HydrateUtils {
|
|
11
11
|
static async hydrateDataFields(model: RWSModel<any>, collections_to_models: { [key: string]: any }, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: { [key: string]: any }) {
|
|
12
12
|
const timeSeriesIds = TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
13
|
+
|
|
14
|
+
// Build a set of foreign key field names to skip
|
|
15
|
+
const foreignKeyFields = new Set<string>();
|
|
16
|
+
for (const relationName in relOneData) {
|
|
17
|
+
const relationMeta = relOneData[relationName];
|
|
18
|
+
if (relationMeta.hydrationField) {
|
|
19
|
+
foreignKeyFields.add(relationMeta.hydrationField);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
13
23
|
for (const key in data) {
|
|
14
24
|
if (data.hasOwnProperty(key)) {
|
|
15
25
|
if (!fullDataMode && ((model).constructor as OpModelType<any>)._CUT_KEYS.includes(key)) {
|
|
16
26
|
continue;
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
// Skip relation property names
|
|
19
30
|
if (Object.keys(relOneData).includes(key)) {
|
|
20
31
|
continue;
|
|
21
32
|
}
|
|
22
33
|
|
|
34
|
+
// Skip foreign key field names
|
|
35
|
+
if (foreignKeyFields.has(key)) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
if (seriesHydrationfields.includes(key)) {
|
|
24
40
|
continue;
|
|
25
41
|
}
|
|
@@ -172,7 +172,7 @@ class DBService {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
async findOneBy(collection: string, conditions: any, fields: string[] | null = null, ordering: OrderByType = null): Promise<IModel|null>
|
|
175
|
+
async findOneBy(collection: string, conditions: any, fields: string[] | null = null, ordering: OrderByType = null, prismaOptions: any = null): Promise<IModel|null>
|
|
176
176
|
{
|
|
177
177
|
const params: any = { where: conditions };
|
|
178
178
|
|
|
@@ -181,6 +181,18 @@ class DBService {
|
|
|
181
181
|
fields.forEach((fieldName: string) => {
|
|
182
182
|
params.select[fieldName] = true;
|
|
183
183
|
});
|
|
184
|
+
|
|
185
|
+
// Add relation fields to select instead of using include when fields are specified
|
|
186
|
+
if(prismaOptions?.include) {
|
|
187
|
+
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
188
|
+
if (fields.includes(relationField)) {
|
|
189
|
+
params.select[relationField] = true;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} else if(prismaOptions?.include) {
|
|
194
|
+
// Only use include when no fields are specified
|
|
195
|
+
params.include = prismaOptions.include;
|
|
184
196
|
}
|
|
185
197
|
|
|
186
198
|
if(ordering){
|
|
@@ -203,7 +215,8 @@ class DBService {
|
|
|
203
215
|
conditions: any,
|
|
204
216
|
fields: string[] | null = null,
|
|
205
217
|
ordering: OrderByType = null,
|
|
206
|
-
pagination: IPaginationParams = null
|
|
218
|
+
pagination: IPaginationParams = null,
|
|
219
|
+
prismaOptions: any = null): Promise<IModel[]>
|
|
207
220
|
{
|
|
208
221
|
const params: any ={ where: conditions };
|
|
209
222
|
|
|
@@ -212,6 +225,18 @@ class DBService {
|
|
|
212
225
|
fields.forEach((fieldName: string) => {
|
|
213
226
|
params.select[fieldName] = true;
|
|
214
227
|
});
|
|
228
|
+
|
|
229
|
+
// Add relation fields to select instead of using include when fields are specified
|
|
230
|
+
if(prismaOptions?.include) {
|
|
231
|
+
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
232
|
+
if (fields.includes(relationField)) {
|
|
233
|
+
params.select[relationField] = true;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
} else if(prismaOptions?.include) {
|
|
238
|
+
// Only use include when no fields are specified
|
|
239
|
+
params.include = prismaOptions.include;
|
|
215
240
|
}
|
|
216
241
|
|
|
217
242
|
if(ordering){
|
|
@@ -303,7 +328,7 @@ class DBService {
|
|
|
303
328
|
return this;
|
|
304
329
|
}
|
|
305
330
|
|
|
306
|
-
public async count<T = any>(opModel: OpModelType<T>, where: {[k: string]: any} = {}): Promise<number>{
|
|
331
|
+
public async count<T = any>(opModel: OpModelType<T>, where: {[k: string]: any} = {}): Promise<number>{
|
|
307
332
|
return await this.getCollectionHandler(opModel._collection).count({where});
|
|
308
333
|
}
|
|
309
334
|
|