@rws-framework/db 4.1.5 → 4.1.7
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/decorators/InverseRelation.d.ts +3 -0
- package/dist/models/core/RWSModel.d.ts +4 -0
- package/dist/models/core/RWSModel.js +58 -24
- package/dist/models/types/RelationTypes.d.ts +3 -0
- package/dist/models/utils/HydrateUtils.d.ts +4 -0
- package/dist/models/utils/HydrateUtils.js +43 -3
- package/dist/models/utils/RelationUtils.js +2 -1
- package/dist/services/DBService.d.ts +0 -5
- package/dist/services/DBService.js +1 -54
- package/package.json +1 -1
- package/src/decorators/InverseRelation.ts +1 -0
- package/src/models/core/RWSModel.ts +67 -26
- package/src/models/types/RelationTypes.ts +2 -1
- package/src/models/utils/HydrateUtils.ts +53 -3
- package/src/models/utils/RelationUtils.ts +2 -1
- package/src/services/DBService.ts +82 -153
|
@@ -7,6 +7,9 @@ export interface InverseRelationOpts {
|
|
|
7
7
|
singular?: boolean;
|
|
8
8
|
relationName?: string;
|
|
9
9
|
mappingName?: string;
|
|
10
|
+
orderBy?: {
|
|
11
|
+
[field: string]: 'asc' | 'desc';
|
|
12
|
+
};
|
|
10
13
|
}
|
|
11
14
|
declare function InverseRelation(inversionModel: () => OpModelType<RWSModel<any>>, sourceModel: () => OpModelType<RWSModel<any>>, relationOptions?: Partial<InverseRelationOpts>): (target: any, key: string) => void;
|
|
12
15
|
export default InverseRelation;
|
|
@@ -17,6 +17,10 @@ declare class RWSModel<T> implements IModel {
|
|
|
17
17
|
static _CUT_KEYS: string[];
|
|
18
18
|
private _relationFields;
|
|
19
19
|
private postLoadExecuted;
|
|
20
|
+
/**
|
|
21
|
+
* Store relation foreign key fields for later hydration
|
|
22
|
+
*/
|
|
23
|
+
private storeRelationFields;
|
|
20
24
|
constructor(data?: any);
|
|
21
25
|
isPostLoadExecuted(): boolean;
|
|
22
26
|
setPostLoadExecuted(): void;
|
|
@@ -30,6 +30,17 @@ class RWSModel {
|
|
|
30
30
|
// Store relation foreign key fields for reload() functionality
|
|
31
31
|
_relationFields = {};
|
|
32
32
|
postLoadExecuted = false;
|
|
33
|
+
/**
|
|
34
|
+
* Store relation foreign key fields for later hydration
|
|
35
|
+
*/
|
|
36
|
+
storeRelationFields(data, relOneData) {
|
|
37
|
+
for (const relationName in relOneData) {
|
|
38
|
+
const relationMeta = relOneData[relationName];
|
|
39
|
+
if (relationMeta.hydrationField && data[relationMeta.hydrationField]) {
|
|
40
|
+
this._relationFields[relationMeta.hydrationField] = data[relationMeta.hydrationField];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
33
44
|
constructor(data = null) {
|
|
34
45
|
if (!this.getCollection()) {
|
|
35
46
|
throw new Error('Model must have a collection defined');
|
|
@@ -109,6 +120,15 @@ class RWSModel {
|
|
|
109
120
|
collections_to_models[model.getCollection()] = model;
|
|
110
121
|
});
|
|
111
122
|
const seriesHydrationfields = [];
|
|
123
|
+
// Always preprocess foreign keys to relation objects, even if allowRelations is false
|
|
124
|
+
// This is necessary because toMongo() expects relation objects to exist
|
|
125
|
+
try {
|
|
126
|
+
data = await HydrateUtils_1.HydrateUtils.preprocessForeignKeys(data, this, relOneData);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Error in preprocessForeignKeys:', error);
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
112
132
|
// Check if relations are already populated from Prisma includes
|
|
113
133
|
const relationsAlreadyPopulated = this.checkRelationsPrePopulated(data, relOneData, relManyData);
|
|
114
134
|
if (allowRelations && !relationsAlreadyPopulated) {
|
|
@@ -118,16 +138,25 @@ class RWSModel {
|
|
|
118
138
|
else if (allowRelations && relationsAlreadyPopulated) {
|
|
119
139
|
// Relations are already populated from Prisma, just assign them directly
|
|
120
140
|
await this.hydratePrePopulatedRelations(data, relOneData, relManyData);
|
|
121
|
-
// Create a copy of data without relation fields to prevent
|
|
141
|
+
// Create a copy of data without relation fields AND foreign key fields to prevent conflicts
|
|
122
142
|
const dataWithoutRelations = { ...data };
|
|
123
143
|
for (const key in relOneData) {
|
|
124
|
-
delete dataWithoutRelations[key];
|
|
144
|
+
delete dataWithoutRelations[key]; // Remove relation field (e.g., 'screenFile')
|
|
145
|
+
const relationMeta = relOneData[key];
|
|
146
|
+
if (relationMeta.hydrationField) {
|
|
147
|
+
delete dataWithoutRelations[relationMeta.hydrationField]; // Remove foreign key field (e.g., 'screen_file_id')
|
|
148
|
+
}
|
|
125
149
|
}
|
|
126
150
|
for (const key in relManyData) {
|
|
127
|
-
delete dataWithoutRelations[key];
|
|
151
|
+
delete dataWithoutRelations[key]; // Remove relation array fields
|
|
128
152
|
}
|
|
129
153
|
data = dataWithoutRelations;
|
|
130
154
|
}
|
|
155
|
+
else {
|
|
156
|
+
// Even if relations are disabled, we still need to store relation foreign key fields
|
|
157
|
+
// for later hydration when reload() is called
|
|
158
|
+
this.storeRelationFields(data, relOneData);
|
|
159
|
+
}
|
|
131
160
|
// Process regular fields and time series (excluding relations when pre-populated)
|
|
132
161
|
await HydrateUtils_1.HydrateUtils.hydrateDataFields(this, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
133
162
|
if (!this.isPostLoadExecuted() && postLoadExecute) {
|
|
@@ -161,33 +190,29 @@ class RWSModel {
|
|
|
161
190
|
// Get relation metadata to determine how to handle each relation
|
|
162
191
|
const classFields = FieldsHelper_1.FieldsHelper.getAllClassFields(this.constructor);
|
|
163
192
|
const relOneData = await this.getRelationOneMeta(classFields);
|
|
193
|
+
// Check if this is a new model (no ID) to handle relations differently during creation
|
|
194
|
+
const isNewModel = !this.id;
|
|
164
195
|
for (const key in this) {
|
|
165
196
|
if (await this.hasRelation(key)) {
|
|
166
197
|
const relationMeta = relOneData[key];
|
|
167
198
|
if (relationMeta) {
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (shouldUseConnect) {
|
|
174
|
-
// Relations with required=true or explicit cascade → use connect
|
|
175
|
-
if (this[key] === null) {
|
|
199
|
+
// Always use Prisma relation syntax when relation objects exist
|
|
200
|
+
// This ensures compatibility with Prisma's expected format
|
|
201
|
+
if (this[key] === null || this[key] === undefined) {
|
|
202
|
+
// Only add disconnect if this is an update (not a creation)
|
|
203
|
+
if (!isNewModel) {
|
|
176
204
|
data[key] = { disconnect: true };
|
|
177
205
|
}
|
|
178
|
-
|
|
179
|
-
data[key] = { connect: { id: this[key].id } };
|
|
180
|
-
}
|
|
206
|
+
// For new models, we simply don't include the relation field
|
|
181
207
|
}
|
|
182
|
-
else {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
208
|
+
else if (this[key] && this[key].id) {
|
|
209
|
+
data[key] = { connect: { id: this[key].id } };
|
|
210
|
+
}
|
|
211
|
+
else if (this[key] && typeof this[key] === 'object' && !this[key].id) {
|
|
212
|
+
// Handle case where we have a relation object but it might not have an ID yet
|
|
213
|
+
// This could happen if the relation object is also being created
|
|
214
|
+
// In this case, we might want to create the relation first or handle it differently
|
|
215
|
+
console.warn(`Relation ${key} has an object without ID. This might cause issues during creation.`);
|
|
191
216
|
}
|
|
192
217
|
}
|
|
193
218
|
continue;
|
|
@@ -375,7 +400,16 @@ class RWSModel {
|
|
|
375
400
|
// Add one-to-many relations (without nesting)
|
|
376
401
|
for (const key in relManyData) {
|
|
377
402
|
if (shouldIncludeRelation(key)) {
|
|
378
|
-
|
|
403
|
+
const relationMeta = relManyData[key];
|
|
404
|
+
if (relationMeta.orderBy) {
|
|
405
|
+
// Add Prisma orderBy to the relation include
|
|
406
|
+
includes[key] = {
|
|
407
|
+
orderBy: relationMeta.orderBy
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
412
|
+
}
|
|
379
413
|
}
|
|
380
414
|
}
|
|
381
415
|
return Object.keys(includes).length > 0 ? includes : null;
|
|
@@ -2,6 +2,10 @@ import { RWSModel } from "../core/RWSModel";
|
|
|
2
2
|
import { RelManyMetaType, RelOneMetaType } from "../types/RelationTypes";
|
|
3
3
|
import { IRWSModel } from "../../types/IRWSModel";
|
|
4
4
|
export declare class HydrateUtils {
|
|
5
|
+
/**
|
|
6
|
+
* Preprocess database data to convert foreign keys to relation objects when relations are not already populated
|
|
7
|
+
*/
|
|
8
|
+
static preprocessForeignKeys(data: any, model: RWSModel<any>, relOneData: RelOneMetaType<IRWSModel>): Promise<any>;
|
|
5
9
|
static hydrateDataFields(model: RWSModel<any>, collections_to_models: {
|
|
6
10
|
[key: string]: any;
|
|
7
11
|
}, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {
|
|
@@ -9,6 +9,41 @@ const RelationUtils_1 = require("./RelationUtils");
|
|
|
9
9
|
const ModelUtils_1 = require("./ModelUtils");
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
11
|
class HydrateUtils {
|
|
12
|
+
/**
|
|
13
|
+
* Preprocess database data to convert foreign keys to relation objects when relations are not already populated
|
|
14
|
+
*/
|
|
15
|
+
static async preprocessForeignKeys(data, model, relOneData) {
|
|
16
|
+
const processedData = { ...data };
|
|
17
|
+
// For each relation, handle different scenarios during creation and updates
|
|
18
|
+
for (const relationName in relOneData) {
|
|
19
|
+
const relationMeta = relOneData[relationName];
|
|
20
|
+
const foreignKeyField = relationMeta.hydrationField; // e.g., "tutorial_id"
|
|
21
|
+
const relationField = relationMeta.key; // e.g., "tutorial"
|
|
22
|
+
// Scenario 1: We have a foreign key value but no relation object (or the relation is just an ID)
|
|
23
|
+
if (foreignKeyField in data && data[foreignKeyField] !== null && data[foreignKeyField] !== undefined &&
|
|
24
|
+
(!data[relationField] || typeof data[relationField] !== 'object')) {
|
|
25
|
+
// Create a minimal relation object with just the ID
|
|
26
|
+
// This allows toMongo() to work properly without requiring full relation loading
|
|
27
|
+
processedData[relationField] = { id: data[foreignKeyField] };
|
|
28
|
+
}
|
|
29
|
+
// Scenario 2: We have a relation object but no foreign key field (common during creation)
|
|
30
|
+
// Ensure the foreign key field exists when we have a valid relation object
|
|
31
|
+
else if (data[relationField] && typeof data[relationField] === 'object' &&
|
|
32
|
+
data[relationField].id &&
|
|
33
|
+
(!(foreignKeyField in data) || data[foreignKeyField] === null || data[foreignKeyField] === undefined)) {
|
|
34
|
+
// Set the foreign key field from the relation object's ID
|
|
35
|
+
processedData[foreignKeyField] = data[relationField].id;
|
|
36
|
+
}
|
|
37
|
+
// Scenario 3: Both relation object and foreign key exist, ensure they're consistent
|
|
38
|
+
else if (data[relationField] && typeof data[relationField] === 'object' &&
|
|
39
|
+
data[relationField].id && foreignKeyField in data &&
|
|
40
|
+
data[foreignKeyField] !== data[relationField].id) {
|
|
41
|
+
// Prioritize the relation object's ID
|
|
42
|
+
processedData[foreignKeyField] = data[relationField].id;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return processedData;
|
|
46
|
+
}
|
|
12
47
|
static async hydrateDataFields(model, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data) {
|
|
13
48
|
const timeSeriesIds = TimeSeriesUtils_1.TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
14
49
|
// Build a set of foreign key field names to skip
|
|
@@ -97,9 +132,14 @@ class HydrateUtils {
|
|
|
97
132
|
const relMeta = relOneData[key];
|
|
98
133
|
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
99
134
|
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
100
|
-
// Only throw error if this is a fresh load
|
|
101
|
-
|
|
102
|
-
|
|
135
|
+
// Only throw error if this is a fresh load AND we're not in creation mode
|
|
136
|
+
// During creation, required relations might not have their foreign key set yet
|
|
137
|
+
if (!model.id && data[relMeta.key]) {
|
|
138
|
+
// We have the relation object but not the foreign key - this is okay during creation
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
else if (!model.id && !data[relMeta.key]) {
|
|
142
|
+
throw new Error(`Required relation "${relMeta.key}" is missing in model ${model.constructor.name}.`);
|
|
103
143
|
}
|
|
104
144
|
// For existing models (reloads), skip loading this relation if the field is missing
|
|
105
145
|
continue;
|
|
@@ -42,7 +42,8 @@ class RelationUtils {
|
|
|
42
42
|
key: resolvedMetadata.key,
|
|
43
43
|
inversionModel: resolvedMetadata.inversionModel,
|
|
44
44
|
foreignKey: resolvedMetadata.foreignKey,
|
|
45
|
-
singular: resolvedMetadata?.singular || false
|
|
45
|
+
singular: resolvedMetadata?.singular || false,
|
|
46
|
+
orderBy: resolvedMetadata?.orderBy || null
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -35,11 +35,6 @@ declare class DBService {
|
|
|
35
35
|
count<T = any>(opModel: OpModelType<T>, where?: {
|
|
36
36
|
[k: string]: any;
|
|
37
37
|
}): Promise<number>;
|
|
38
|
-
/**
|
|
39
|
-
* Convert foreign key fields to Prisma relation syntax
|
|
40
|
-
* Handles common patterns like user_id -> creator, avatar_id -> avatar, etc.
|
|
41
|
-
*/
|
|
42
|
-
private convertForeignKeysToRelations;
|
|
43
38
|
getPrismaClient(): PrismaClient;
|
|
44
39
|
}
|
|
45
40
|
export { DBService, IDBClientCreate };
|
|
@@ -120,11 +120,9 @@ class DBService {
|
|
|
120
120
|
delete data[cKey];
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
// Convert foreign key fields to Prisma relation syntax
|
|
124
|
-
const processedData = this.convertForeignKeysToRelations(data);
|
|
125
123
|
await prismaCollection.update({
|
|
126
124
|
where,
|
|
127
|
-
data
|
|
125
|
+
data,
|
|
128
126
|
});
|
|
129
127
|
return await this.findOneBy(collection, where);
|
|
130
128
|
}
|
|
@@ -246,57 +244,6 @@ class DBService {
|
|
|
246
244
|
async count(opModel, where = {}) {
|
|
247
245
|
return await this.getCollectionHandler(opModel._collection).count({ where });
|
|
248
246
|
}
|
|
249
|
-
/**
|
|
250
|
-
* Convert foreign key fields to Prisma relation syntax
|
|
251
|
-
* Handles common patterns like user_id -> creator, avatar_id -> avatar, etc.
|
|
252
|
-
*/
|
|
253
|
-
convertForeignKeysToRelations(data) {
|
|
254
|
-
const processedData = { ...data };
|
|
255
|
-
const relationMappings = {
|
|
256
|
-
// Common relation mappings for foreign keys to relation names
|
|
257
|
-
'user_id': 'creator',
|
|
258
|
-
'avatar_id': 'avatar',
|
|
259
|
-
'file_id': 'logo',
|
|
260
|
-
'company_id': 'company',
|
|
261
|
-
'user_group_id': 'userGroup',
|
|
262
|
-
'accountGrade_id': 'accountGrade',
|
|
263
|
-
'account_balance_id': 'accountBalance',
|
|
264
|
-
'knowledgeGroup_id': 'project',
|
|
265
|
-
'conversation_id': 'conversation',
|
|
266
|
-
'message_id': 'message',
|
|
267
|
-
'knowledge_id': 'knowledge',
|
|
268
|
-
'profession_id': 'profession',
|
|
269
|
-
'step_id': 'step',
|
|
270
|
-
'question_id': 'question',
|
|
271
|
-
'tutorial_id': 'tutorial',
|
|
272
|
-
'tutorial_step_id': 'tutorialStep',
|
|
273
|
-
'tutorial_section_id': 'tutorialSection',
|
|
274
|
-
'todo_id': 'todo',
|
|
275
|
-
'bot_test_id': 'botTest',
|
|
276
|
-
'bot_test_tester_id': 'tester',
|
|
277
|
-
'bot_test_target_id': 'target',
|
|
278
|
-
'branch_id': 'branch',
|
|
279
|
-
'acl_policy_id': 'policy',
|
|
280
|
-
'instruction_file_id': 'instructionFile'
|
|
281
|
-
};
|
|
282
|
-
// Convert foreign key fields to relation syntax
|
|
283
|
-
Object.keys(processedData).forEach(key => {
|
|
284
|
-
if (key.endsWith('_id') && relationMappings[key]) {
|
|
285
|
-
const relationField = relationMappings[key];
|
|
286
|
-
const value = processedData[key];
|
|
287
|
-
// Remove the foreign key field
|
|
288
|
-
delete processedData[key];
|
|
289
|
-
// Add the relation field with proper Prisma syntax
|
|
290
|
-
if (value === null || value === undefined) {
|
|
291
|
-
processedData[relationField] = { disconnect: true };
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
processedData[relationField] = { connect: { id: value } };
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
return processedData;
|
|
299
|
-
}
|
|
300
247
|
getPrismaClient() {
|
|
301
248
|
if (!this.client || !this.connected) {
|
|
302
249
|
this.connectToDB();
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ export interface InverseRelationOpts {
|
|
|
9
9
|
singular?: boolean
|
|
10
10
|
relationName?: string
|
|
11
11
|
mappingName?: string
|
|
12
|
+
orderBy?: { [field: string]: 'asc' | 'desc' }
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function guessForeignKey(inversionModel: OpModelType<RWSModel<any>>, bindingModel: OpModelType<RWSModel<any>>, decoratorsData: any)
|
|
@@ -33,6 +33,18 @@ class RWSModel<T> implements IModel {
|
|
|
33
33
|
|
|
34
34
|
private postLoadExecuted: boolean = false;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Store relation foreign key fields for later hydration
|
|
38
|
+
*/
|
|
39
|
+
private storeRelationFields(data: any, relOneData: any): void {
|
|
40
|
+
for (const relationName in relOneData) {
|
|
41
|
+
const relationMeta = relOneData[relationName];
|
|
42
|
+
if (relationMeta.hydrationField && data[relationMeta.hydrationField]) {
|
|
43
|
+
this._relationFields[relationMeta.hydrationField] = data[relationMeta.hydrationField];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
constructor(data: any = null) {
|
|
37
49
|
if(!this.getCollection()){
|
|
38
50
|
throw new Error('Model must have a collection defined');
|
|
@@ -130,10 +142,20 @@ class RWSModel<T> implements IModel {
|
|
|
130
142
|
collections_to_models[model.getCollection()] = model;
|
|
131
143
|
});
|
|
132
144
|
|
|
133
|
-
const seriesHydrationfields: string[] = [];
|
|
145
|
+
const seriesHydrationfields: string[] = [];
|
|
146
|
+
|
|
147
|
+
// Always preprocess foreign keys to relation objects, even if allowRelations is false
|
|
148
|
+
// This is necessary because toMongo() expects relation objects to exist
|
|
149
|
+
try {
|
|
150
|
+
data = await HydrateUtils.preprocessForeignKeys(data, this, relOneData);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error('Error in preprocessForeignKeys:', error);
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
134
155
|
|
|
135
156
|
// Check if relations are already populated from Prisma includes
|
|
136
157
|
const relationsAlreadyPopulated = this.checkRelationsPrePopulated(data, relOneData, relManyData);
|
|
158
|
+
|
|
137
159
|
|
|
138
160
|
if (allowRelations && !relationsAlreadyPopulated) {
|
|
139
161
|
// Use traditional relation hydration if not pre-populated
|
|
@@ -142,15 +164,24 @@ class RWSModel<T> implements IModel {
|
|
|
142
164
|
// Relations are already populated from Prisma, just assign them directly
|
|
143
165
|
await this.hydratePrePopulatedRelations(data, relOneData, relManyData);
|
|
144
166
|
|
|
145
|
-
// Create a copy of data without relation fields to prevent
|
|
167
|
+
// Create a copy of data without relation fields AND foreign key fields to prevent conflicts
|
|
146
168
|
const dataWithoutRelations = { ...data };
|
|
147
169
|
for (const key in relOneData) {
|
|
148
|
-
delete dataWithoutRelations[key];
|
|
170
|
+
delete dataWithoutRelations[key]; // Remove relation field (e.g., 'screenFile')
|
|
171
|
+
|
|
172
|
+
const relationMeta = relOneData[key];
|
|
173
|
+
if (relationMeta.hydrationField) {
|
|
174
|
+
delete dataWithoutRelations[relationMeta.hydrationField]; // Remove foreign key field (e.g., 'screen_file_id')
|
|
175
|
+
}
|
|
149
176
|
}
|
|
150
177
|
for (const key in relManyData) {
|
|
151
|
-
delete dataWithoutRelations[key];
|
|
178
|
+
delete dataWithoutRelations[key]; // Remove relation array fields
|
|
152
179
|
}
|
|
153
180
|
data = dataWithoutRelations;
|
|
181
|
+
} else {
|
|
182
|
+
// Even if relations are disabled, we still need to store relation foreign key fields
|
|
183
|
+
// for later hydration when reload() is called
|
|
184
|
+
this.storeRelationFields(data, relOneData);
|
|
154
185
|
}
|
|
155
186
|
|
|
156
187
|
// Process regular fields and time series (excluding relations when pre-populated)
|
|
@@ -195,6 +226,7 @@ class RWSModel<T> implements IModel {
|
|
|
195
226
|
}
|
|
196
227
|
|
|
197
228
|
public async toMongo(): Promise<any> {
|
|
229
|
+
|
|
198
230
|
const data: any = {};
|
|
199
231
|
const timeSeriesIds = TimeSeriesUtils.getTimeSeriesModelFields(this);
|
|
200
232
|
const timeSeriesHydrationFields: string[] = [];
|
|
@@ -202,33 +234,33 @@ class RWSModel<T> implements IModel {
|
|
|
202
234
|
// Get relation metadata to determine how to handle each relation
|
|
203
235
|
const classFields = FieldsHelper.getAllClassFields(this.constructor);
|
|
204
236
|
const relOneData = await this.getRelationOneMeta(classFields);
|
|
205
|
-
|
|
237
|
+
|
|
238
|
+
// Check if this is a new model (no ID) to handle relations differently during creation
|
|
239
|
+
const isNewModel = !this.id;
|
|
240
|
+
|
|
206
241
|
for (const key in (this as any)) {
|
|
242
|
+
|
|
207
243
|
if (await this.hasRelation(key)) {
|
|
208
244
|
const relationMeta = relOneData[key];
|
|
209
245
|
|
|
210
246
|
if (relationMeta) {
|
|
211
|
-
// Use connect on relations that are either:
|
|
212
|
-
// 1. Required (required: true)
|
|
213
|
-
// 2. Have explicitly set cascade options (metaOpts.cascade)
|
|
214
|
-
const hasExplicitCascade = relationMeta.cascade && Object.keys(relationMeta.cascade).length > 0;
|
|
215
|
-
const shouldUseConnect = relationMeta.required || hasExplicitCascade;
|
|
216
247
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
248
|
+
// Always use Prisma relation syntax when relation objects exist
|
|
249
|
+
// This ensures compatibility with Prisma's expected format
|
|
250
|
+
|
|
251
|
+
if (this[key] === null || this[key] === undefined) {
|
|
252
|
+
// Only add disconnect if this is an update (not a creation)
|
|
253
|
+
if (!isNewModel) {
|
|
220
254
|
data[key] = { disconnect: true };
|
|
221
|
-
} else if (this[key] && this[key].id) {
|
|
222
|
-
data[key] = { connect: { id: this[key].id } };
|
|
223
|
-
}
|
|
224
|
-
} else {
|
|
225
|
-
// Simple optional relations → use foreign key field directly
|
|
226
|
-
const foreignKeyField = relationMeta.hydrationField;
|
|
227
|
-
if (this[key] === null) {
|
|
228
|
-
data[foreignKeyField] = null;
|
|
229
|
-
} else if (this[key] && this[key].id) {
|
|
230
|
-
data[foreignKeyField] = this[key].id;
|
|
231
255
|
}
|
|
256
|
+
// For new models, we simply don't include the relation field
|
|
257
|
+
} else if (this[key] && this[key].id) {
|
|
258
|
+
data[key] = { connect: { id: this[key].id } };
|
|
259
|
+
} else if (this[key] && typeof this[key] === 'object' && !this[key].id) {
|
|
260
|
+
// Handle case where we have a relation object but it might not have an ID yet
|
|
261
|
+
// This could happen if the relation object is also being created
|
|
262
|
+
// In this case, we might want to create the relation first or handle it differently
|
|
263
|
+
console.warn(`Relation ${key} has an object without ID. This might cause issues during creation.`);
|
|
232
264
|
}
|
|
233
265
|
}
|
|
234
266
|
|
|
@@ -256,7 +288,7 @@ class RWSModel<T> implements IModel {
|
|
|
256
288
|
timeSeriesHydrationFields.push(timeSeriesIds[key].hydrationField);
|
|
257
289
|
}
|
|
258
290
|
}
|
|
259
|
-
|
|
291
|
+
|
|
260
292
|
return data;
|
|
261
293
|
}
|
|
262
294
|
|
|
@@ -268,8 +300,9 @@ class RWSModel<T> implements IModel {
|
|
|
268
300
|
return (this as any).constructor._collection || this._collection;
|
|
269
301
|
}
|
|
270
302
|
|
|
271
|
-
async save(): Promise<this> {
|
|
303
|
+
async save(): Promise<this> {
|
|
272
304
|
const data = await this.toMongo();
|
|
305
|
+
|
|
273
306
|
let updatedModelData = data;
|
|
274
307
|
|
|
275
308
|
const entryExists = await ModelUtils.entryExists(this);
|
|
@@ -495,7 +528,15 @@ class RWSModel<T> implements IModel {
|
|
|
495
528
|
// Add one-to-many relations (without nesting)
|
|
496
529
|
for (const key in relManyData) {
|
|
497
530
|
if (shouldIncludeRelation(key)) {
|
|
498
|
-
|
|
531
|
+
const relationMeta = relManyData[key];
|
|
532
|
+
if (relationMeta.orderBy) {
|
|
533
|
+
// Add Prisma orderBy to the relation include
|
|
534
|
+
includes[key] = {
|
|
535
|
+
orderBy: relationMeta.orderBy
|
|
536
|
+
};
|
|
537
|
+
} else {
|
|
538
|
+
includes[key] = true; // Only load this level, no nested relations
|
|
539
|
+
}
|
|
499
540
|
}
|
|
500
541
|
}
|
|
501
542
|
|
|
@@ -9,7 +9,52 @@ import { FieldsHelper } from "../../helper/FieldsHelper";
|
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
|
|
11
11
|
export class HydrateUtils {
|
|
12
|
+
/**
|
|
13
|
+
* Preprocess database data to convert foreign keys to relation objects when relations are not already populated
|
|
14
|
+
*/
|
|
15
|
+
static async preprocessForeignKeys(data: any, model: RWSModel<any>, relOneData: RelOneMetaType<IRWSModel>): Promise<any> {
|
|
16
|
+
const processedData = { ...data };
|
|
17
|
+
|
|
18
|
+
// For each relation, handle different scenarios during creation and updates
|
|
19
|
+
for (const relationName in relOneData) {
|
|
20
|
+
const relationMeta = relOneData[relationName];
|
|
21
|
+
const foreignKeyField = relationMeta.hydrationField; // e.g., "tutorial_id"
|
|
22
|
+
const relationField = relationMeta.key; // e.g., "tutorial"
|
|
23
|
+
|
|
24
|
+
// Scenario 1: We have a foreign key value but no relation object (or the relation is just an ID)
|
|
25
|
+
if (foreignKeyField in data && data[foreignKeyField] !== null && data[foreignKeyField] !== undefined &&
|
|
26
|
+
(!data[relationField] || typeof data[relationField] !== 'object')) {
|
|
27
|
+
|
|
28
|
+
// Create a minimal relation object with just the ID
|
|
29
|
+
// This allows toMongo() to work properly without requiring full relation loading
|
|
30
|
+
processedData[relationField] = { id: data[foreignKeyField] };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Scenario 2: We have a relation object but no foreign key field (common during creation)
|
|
34
|
+
// Ensure the foreign key field exists when we have a valid relation object
|
|
35
|
+
else if (data[relationField] && typeof data[relationField] === 'object' &&
|
|
36
|
+
data[relationField].id &&
|
|
37
|
+
(!(foreignKeyField in data) || data[foreignKeyField] === null || data[foreignKeyField] === undefined)) {
|
|
38
|
+
|
|
39
|
+
// Set the foreign key field from the relation object's ID
|
|
40
|
+
processedData[foreignKeyField] = data[relationField].id;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Scenario 3: Both relation object and foreign key exist, ensure they're consistent
|
|
44
|
+
else if (data[relationField] && typeof data[relationField] === 'object' &&
|
|
45
|
+
data[relationField].id && foreignKeyField in data &&
|
|
46
|
+
data[foreignKeyField] !== data[relationField].id) {
|
|
47
|
+
|
|
48
|
+
// Prioritize the relation object's ID
|
|
49
|
+
processedData[foreignKeyField] = data[relationField].id;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return processedData;
|
|
54
|
+
}
|
|
55
|
+
|
|
12
56
|
static async hydrateDataFields(model: RWSModel<any>, collections_to_models: { [key: string]: any }, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: { [key: string]: any }) {
|
|
57
|
+
|
|
13
58
|
const timeSeriesIds = TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
14
59
|
|
|
15
60
|
// Build a set of foreign key field names to skip
|
|
@@ -26,6 +71,7 @@ export class HydrateUtils {
|
|
|
26
71
|
|
|
27
72
|
for (const key in data) {
|
|
28
73
|
if (data.hasOwnProperty(key)) {
|
|
74
|
+
|
|
29
75
|
if (!fullDataMode && ignoredKeys.includes(key)) {
|
|
30
76
|
continue;
|
|
31
77
|
}
|
|
@@ -117,9 +163,13 @@ export class HydrateUtils {
|
|
|
117
163
|
const relationEnabled = !RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
118
164
|
|
|
119
165
|
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
120
|
-
// Only throw error if this is a fresh load
|
|
121
|
-
|
|
122
|
-
|
|
166
|
+
// Only throw error if this is a fresh load AND we're not in creation mode
|
|
167
|
+
// During creation, required relations might not have their foreign key set yet
|
|
168
|
+
if (!model.id && data[relMeta.key]) {
|
|
169
|
+
// We have the relation object but not the foreign key - this is okay during creation
|
|
170
|
+
continue;
|
|
171
|
+
} else if (!model.id && !data[relMeta.key]) {
|
|
172
|
+
throw new Error(`Required relation "${relMeta.key}" is missing in model ${model.constructor.name}.`);
|
|
123
173
|
}
|
|
124
174
|
// For existing models (reloads), skip loading this relation if the field is missing
|
|
125
175
|
continue;
|
|
@@ -51,7 +51,8 @@ export class RelationUtils {
|
|
|
51
51
|
key: resolvedMetadata.key,
|
|
52
52
|
inversionModel: resolvedMetadata.inversionModel,
|
|
53
53
|
foreignKey: resolvedMetadata.foreignKey,
|
|
54
|
-
singular: resolvedMetadata?.singular || false
|
|
54
|
+
singular: resolvedMetadata?.singular || false,
|
|
55
|
+
orderBy: resolvedMetadata?.orderBy || null
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { PrismaClient } from '@prisma/client';
|
|
2
2
|
import { Collection, Db, MongoClient } from 'mongodb';
|
|
3
|
-
import {ITimeSeries} from '../types/ITimeSeries';
|
|
3
|
+
import { ITimeSeries } from '../types/ITimeSeries';
|
|
4
4
|
import { IModel } from '../models/interfaces/IModel';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import { IDbConfigHandler } from '../types/DbConfigHandler';
|
|
7
7
|
import { IPaginationParams, OrderByType, OrderByField, OrderByArray } from '../types/FindParams';
|
|
8
8
|
import { OpModelType } from '../models/interfaces/OpModelType';
|
|
9
|
+
import { ModelUtils } from '../models/utils/ModelUtils';
|
|
9
10
|
|
|
10
11
|
interface IDBClientCreate {
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
dbUrl?: string;
|
|
13
|
+
dbName?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
class DBService {
|
|
@@ -17,66 +18,62 @@ class DBService {
|
|
|
17
18
|
private opts: IDBClientCreate = null;
|
|
18
19
|
private connected = false;
|
|
19
20
|
|
|
20
|
-
constructor(private configService: IDbConfigHandler){}
|
|
21
|
+
constructor(private configService: IDbConfigHandler) { }
|
|
21
22
|
|
|
22
23
|
private connectToDB(opts: IDBClientCreate = null) {
|
|
23
|
-
if(opts){
|
|
24
|
+
if (opts) {
|
|
24
25
|
this.opts = opts;
|
|
25
|
-
}else{
|
|
26
|
+
} else {
|
|
26
27
|
this.opts = {
|
|
27
|
-
dbUrl: this.configService.get('db_url'),
|
|
28
|
+
dbUrl: this.configService.get('db_url'),
|
|
28
29
|
dbName: this.configService.get('db_name'),
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
if(!this.opts.dbUrl){
|
|
33
|
+
if (!this.opts.dbUrl) {
|
|
33
34
|
console.log(chalk.red('No database config set in @rws-framework/db'));
|
|
34
35
|
|
|
35
36
|
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
try{
|
|
39
|
-
this.client = new PrismaClient({
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
this.client = new PrismaClient({
|
|
40
41
|
datasources: {
|
|
41
42
|
db: {
|
|
42
43
|
url: this.opts.dbUrl
|
|
43
44
|
},
|
|
44
45
|
},
|
|
45
|
-
});
|
|
46
|
+
});
|
|
46
47
|
|
|
47
48
|
this.connected = true;
|
|
48
|
-
} catch (e: Error | any){
|
|
49
|
+
} catch (e: Error | any) {
|
|
49
50
|
console.error(e);
|
|
50
|
-
|
|
51
|
+
|
|
51
52
|
throw new Error('PRISMA CONNECTION ERROR');
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
reconnect(opts: IDBClientCreate = null)
|
|
56
|
-
{
|
|
56
|
+
reconnect(opts: IDBClientCreate = null) {
|
|
57
57
|
this.connectToDB(opts);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
static baseClientConstruct(dbUrl: string): MongoClient
|
|
61
|
-
{
|
|
60
|
+
static baseClientConstruct(dbUrl: string): MongoClient {
|
|
62
61
|
const client = new MongoClient(dbUrl);
|
|
63
62
|
|
|
64
63
|
return client;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
public async createBaseMongoClient(): Promise<MongoClient>
|
|
68
|
-
{
|
|
66
|
+
public async createBaseMongoClient(): Promise<MongoClient> {
|
|
69
67
|
const dbUrl = this.opts?.dbUrl || this.configService.get('db_url');
|
|
70
68
|
const client = DBService.baseClientConstruct(dbUrl);
|
|
71
|
-
|
|
69
|
+
|
|
72
70
|
await client.connect();
|
|
73
71
|
|
|
74
72
|
return client;
|
|
75
73
|
|
|
76
74
|
}
|
|
77
75
|
|
|
78
|
-
public async createBaseMongoClientDB(): Promise<[MongoClient, Db]>
|
|
79
|
-
{
|
|
76
|
+
public async createBaseMongoClientDB(): Promise<[MongoClient, Db]> {
|
|
80
77
|
const dbName = this.opts?.dbName || this.configService.get('db_name');
|
|
81
78
|
const client = await this.createBaseMongoClient();
|
|
82
79
|
return [client, client.db(dbName)];
|
|
@@ -101,104 +98,98 @@ class DBService {
|
|
|
101
98
|
await client.close();
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
async watchCollection(collectionName: string, preRun: () => void): Promise<any>
|
|
105
|
-
{
|
|
101
|
+
async watchCollection(collectionName: string, preRun: () => void): Promise<any> {
|
|
106
102
|
const [client, db] = await this.createBaseMongoClientDB();
|
|
107
103
|
const collection = db.collection(collectionName);
|
|
108
104
|
|
|
109
|
-
const changeStream = collection.watch();
|
|
110
|
-
return new Promise((resolve) => {
|
|
111
|
-
changeStream.on('change', (change) => {
|
|
105
|
+
const changeStream = collection.watch();
|
|
106
|
+
return new Promise((resolve) => {
|
|
107
|
+
changeStream.on('change', (change) => {
|
|
112
108
|
resolve(change);
|
|
113
109
|
});
|
|
114
110
|
|
|
115
111
|
preRun();
|
|
116
|
-
});
|
|
112
|
+
});
|
|
117
113
|
}
|
|
118
114
|
|
|
119
115
|
async insert(data: any, collection: string, isTimeSeries: boolean = false) {
|
|
120
|
-
|
|
116
|
+
|
|
121
117
|
let result: any = data;
|
|
122
118
|
// Insert time-series data outside of the transaction
|
|
123
119
|
|
|
124
|
-
if(isTimeSeries){
|
|
120
|
+
if (isTimeSeries) {
|
|
125
121
|
const [client, db] = await this.createBaseMongoClientDB();
|
|
126
122
|
const collectionHandler = db.collection(collection);
|
|
127
|
-
|
|
123
|
+
|
|
128
124
|
const insert = await collectionHandler.insertOne(data);
|
|
129
125
|
|
|
130
|
-
result = await this.findOneBy(collection, { id: insert.insertedId.toString()
|
|
126
|
+
result = await this.findOneBy(collection, { id: insert.insertedId.toString() });
|
|
131
127
|
return result;
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
const prismaCollection = this.getCollectionHandler(collection);
|
|
130
|
+
const prismaCollection = this.getCollectionHandler(collection);
|
|
135
131
|
|
|
136
132
|
result = await prismaCollection.create({ data });
|
|
137
133
|
|
|
138
134
|
return await this.findOneBy(collection, { id: result.id });
|
|
139
135
|
}
|
|
140
136
|
|
|
141
|
-
async update(data: any, collection: string, pk: string | string[]): Promise<IModel>
|
|
142
|
-
{
|
|
137
|
+
async update(data: any, collection: string, pk: string | string[]): Promise<IModel> {
|
|
143
138
|
|
|
144
139
|
const prismaCollection = this.getCollectionHandler(collection);
|
|
145
140
|
|
|
146
141
|
|
|
147
142
|
const where: any = {};
|
|
148
|
-
|
|
149
|
-
if(Array.isArray(pk)){
|
|
150
|
-
for(const pkElem of pk){
|
|
143
|
+
|
|
144
|
+
if (Array.isArray(pk)) {
|
|
145
|
+
for (const pkElem of pk) {
|
|
151
146
|
where[pkElem] = data[pkElem];
|
|
152
147
|
}
|
|
153
|
-
}else{
|
|
148
|
+
} else {
|
|
154
149
|
where[pk as string] = data[pk as string]
|
|
155
|
-
}
|
|
150
|
+
}
|
|
156
151
|
|
|
157
|
-
if(!Array.isArray(pk)){
|
|
152
|
+
if (!Array.isArray(pk)) {
|
|
158
153
|
delete data[pk];
|
|
159
|
-
}else{
|
|
160
|
-
for(const cKey in pk){
|
|
154
|
+
} else {
|
|
155
|
+
for (const cKey in pk) {
|
|
161
156
|
delete data[cKey];
|
|
162
157
|
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Convert foreign key fields to Prisma relation syntax
|
|
166
|
-
const processedData = this.convertForeignKeysToRelations(data);
|
|
158
|
+
}
|
|
167
159
|
|
|
168
160
|
await prismaCollection.update({
|
|
169
161
|
where,
|
|
170
|
-
data
|
|
171
|
-
});
|
|
162
|
+
data,
|
|
163
|
+
});
|
|
164
|
+
|
|
172
165
|
|
|
173
|
-
|
|
174
166
|
return await this.findOneBy(collection, where);
|
|
175
167
|
}
|
|
176
|
-
|
|
177
168
|
|
|
178
|
-
|
|
179
|
-
{
|
|
169
|
+
|
|
170
|
+
async findOneBy(collection: string, conditions: any, fields: string[] | null = null, ordering: OrderByType = null, prismaOptions: any = null): Promise<IModel | null> {
|
|
180
171
|
const params: any = { where: conditions };
|
|
181
172
|
|
|
182
|
-
if(fields){
|
|
173
|
+
if (fields) {
|
|
183
174
|
params.select = {};
|
|
184
|
-
fields.forEach((fieldName: string) => {
|
|
175
|
+
fields.forEach((fieldName: string) => {
|
|
185
176
|
params.select[fieldName] = true;
|
|
186
|
-
});
|
|
187
|
-
|
|
177
|
+
});
|
|
178
|
+
|
|
188
179
|
// Add relation fields to select instead of using include when fields are specified
|
|
189
|
-
if(prismaOptions?.include) {
|
|
180
|
+
if (prismaOptions?.include) {
|
|
190
181
|
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
191
182
|
if (fields.includes(relationField)) {
|
|
192
183
|
params.select[relationField] = true;
|
|
193
184
|
}
|
|
194
185
|
});
|
|
195
186
|
}
|
|
196
|
-
} else if(prismaOptions?.include) {
|
|
187
|
+
} else if (prismaOptions?.include) {
|
|
197
188
|
// Only use include when no fields are specified
|
|
198
189
|
params.include = prismaOptions.include;
|
|
199
190
|
}
|
|
200
191
|
|
|
201
|
-
if(ordering){
|
|
192
|
+
if (ordering) {
|
|
202
193
|
params.orderBy = this.convertOrderingToPrismaFormat(ordering);
|
|
203
194
|
}
|
|
204
195
|
|
|
@@ -207,63 +198,60 @@ class DBService {
|
|
|
207
198
|
return retData;
|
|
208
199
|
}
|
|
209
200
|
|
|
210
|
-
async delete(collection: string, conditions: any): Promise<void>
|
|
211
|
-
{
|
|
201
|
+
async delete(collection: string, conditions: any): Promise<void> {
|
|
212
202
|
await this.getCollectionHandler(collection).deleteMany({ where: conditions });
|
|
213
203
|
return;
|
|
214
204
|
}
|
|
215
205
|
|
|
216
206
|
async findBy(
|
|
217
|
-
collection: string,
|
|
218
|
-
conditions: any,
|
|
219
|
-
fields: string[] | null = null,
|
|
220
|
-
ordering: OrderByType = null,
|
|
207
|
+
collection: string,
|
|
208
|
+
conditions: any,
|
|
209
|
+
fields: string[] | null = null,
|
|
210
|
+
ordering: OrderByType = null,
|
|
221
211
|
pagination: IPaginationParams = null,
|
|
222
|
-
prismaOptions: any = null): Promise<IModel[]>
|
|
223
|
-
|
|
224
|
-
const params: any ={ where: conditions };
|
|
212
|
+
prismaOptions: any = null): Promise<IModel[]> {
|
|
213
|
+
const params: any = { where: conditions };
|
|
225
214
|
|
|
226
|
-
if(fields){
|
|
215
|
+
if (fields) {
|
|
227
216
|
params.select = {};
|
|
228
|
-
fields.forEach((fieldName: string) => {
|
|
217
|
+
fields.forEach((fieldName: string) => {
|
|
229
218
|
params.select[fieldName] = true;
|
|
230
|
-
});
|
|
231
|
-
|
|
219
|
+
});
|
|
220
|
+
|
|
232
221
|
// Add relation fields to select instead of using include when fields are specified
|
|
233
|
-
if(prismaOptions?.include) {
|
|
222
|
+
if (prismaOptions?.include) {
|
|
234
223
|
Object.keys(prismaOptions.include).forEach(relationField => {
|
|
235
224
|
if (fields.includes(relationField)) {
|
|
236
225
|
params.select[relationField] = true;
|
|
237
226
|
}
|
|
238
227
|
});
|
|
239
228
|
}
|
|
240
|
-
} else if(prismaOptions?.include) {
|
|
229
|
+
} else if (prismaOptions?.include) {
|
|
241
230
|
// Only use include when no fields are specified
|
|
242
231
|
params.include = prismaOptions.include;
|
|
243
232
|
}
|
|
244
233
|
|
|
245
|
-
if(ordering){
|
|
234
|
+
if (ordering) {
|
|
246
235
|
params.orderBy = this.convertOrderingToPrismaFormat(ordering);
|
|
247
|
-
}
|
|
236
|
+
}
|
|
248
237
|
|
|
249
|
-
if(pagination){
|
|
238
|
+
if (pagination) {
|
|
250
239
|
const perPage = pagination.per_page || 50;
|
|
251
240
|
params.skip = (pagination.page || 0) * perPage;
|
|
252
241
|
params.take = perPage;
|
|
253
242
|
}
|
|
254
243
|
|
|
255
|
-
const retData = await this.getCollectionHandler(collection).findMany(params);
|
|
244
|
+
const retData = await this.getCollectionHandler(collection).findMany(params);
|
|
256
245
|
|
|
257
246
|
return retData;
|
|
258
247
|
}
|
|
259
248
|
|
|
260
|
-
async collectionExists(collection_name: string): Promise<boolean>
|
|
261
|
-
{
|
|
249
|
+
async collectionExists(collection_name: string): Promise<boolean> {
|
|
262
250
|
const dbUrl = this.opts?.dbUrl || this.configService.get('db_url');
|
|
263
251
|
const client = new MongoClient(dbUrl);
|
|
264
252
|
|
|
265
253
|
try {
|
|
266
|
-
await client.connect();
|
|
254
|
+
await client.connect();
|
|
267
255
|
|
|
268
256
|
const db = client.db(this.configService.get('db_name'));
|
|
269
257
|
|
|
@@ -275,12 +263,11 @@ class DBService {
|
|
|
275
263
|
console.error('Error connecting to MongoDB:', error);
|
|
276
264
|
|
|
277
265
|
throw error;
|
|
278
|
-
}
|
|
266
|
+
}
|
|
279
267
|
}
|
|
280
268
|
|
|
281
|
-
async createTimeSeriesCollection(collection_name: string): Promise<Collection<ITimeSeries>>
|
|
282
|
-
|
|
283
|
-
try {
|
|
269
|
+
async createTimeSeriesCollection(collection_name: string): Promise<Collection<ITimeSeries>> {
|
|
270
|
+
try {
|
|
284
271
|
const [client, db] = await this.createBaseMongoClientDB();
|
|
285
272
|
|
|
286
273
|
// Create a time series collection
|
|
@@ -302,9 +289,8 @@ class DBService {
|
|
|
302
289
|
}
|
|
303
290
|
}
|
|
304
291
|
|
|
305
|
-
private getCollectionHandler(collection: string): any
|
|
306
|
-
|
|
307
|
-
if(!this.client || !this.connected){
|
|
292
|
+
private getCollectionHandler(collection: string): any {
|
|
293
|
+
if (!this.client || !this.connected) {
|
|
308
294
|
this.connectToDB();
|
|
309
295
|
}
|
|
310
296
|
|
|
@@ -325,74 +311,17 @@ class DBService {
|
|
|
325
311
|
return [ordering];
|
|
326
312
|
}
|
|
327
313
|
|
|
328
|
-
private setOpts(opts: IDBClientCreate = null): this
|
|
329
|
-
{
|
|
314
|
+
private setOpts(opts: IDBClientCreate = null): this {
|
|
330
315
|
this.opts = opts;
|
|
331
316
|
return this;
|
|
332
317
|
}
|
|
333
318
|
|
|
334
|
-
public async count<T = any>(opModel: OpModelType<T>, where: {[k: string]: any} = {}): Promise<number>{
|
|
335
|
-
return await this.getCollectionHandler(opModel._collection).count({where});
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Convert foreign key fields to Prisma relation syntax
|
|
340
|
-
* Handles common patterns like user_id -> creator, avatar_id -> avatar, etc.
|
|
341
|
-
*/
|
|
342
|
-
private convertForeignKeysToRelations(data: any): any {
|
|
343
|
-
const processedData = { ...data };
|
|
344
|
-
const relationMappings: { [key: string]: string } = {
|
|
345
|
-
// Common relation mappings for foreign keys to relation names
|
|
346
|
-
'user_id': 'creator',
|
|
347
|
-
'avatar_id': 'avatar',
|
|
348
|
-
'file_id': 'logo',
|
|
349
|
-
'company_id': 'company',
|
|
350
|
-
'user_group_id': 'userGroup',
|
|
351
|
-
'accountGrade_id': 'accountGrade',
|
|
352
|
-
'account_balance_id': 'accountBalance',
|
|
353
|
-
'knowledgeGroup_id': 'project',
|
|
354
|
-
'conversation_id': 'conversation',
|
|
355
|
-
'message_id': 'message',
|
|
356
|
-
'knowledge_id': 'knowledge',
|
|
357
|
-
'profession_id': 'profession',
|
|
358
|
-
'step_id': 'step',
|
|
359
|
-
'question_id': 'question',
|
|
360
|
-
'tutorial_id': 'tutorial',
|
|
361
|
-
'tutorial_step_id': 'tutorialStep',
|
|
362
|
-
'tutorial_section_id': 'tutorialSection',
|
|
363
|
-
'todo_id': 'todo',
|
|
364
|
-
'bot_test_id': 'botTest',
|
|
365
|
-
'bot_test_tester_id': 'tester',
|
|
366
|
-
'bot_test_target_id': 'target',
|
|
367
|
-
'branch_id': 'branch',
|
|
368
|
-
'acl_policy_id': 'policy',
|
|
369
|
-
'instruction_file_id': 'instructionFile'
|
|
370
|
-
};
|
|
371
|
-
|
|
372
|
-
// Convert foreign key fields to relation syntax
|
|
373
|
-
Object.keys(processedData).forEach(key => {
|
|
374
|
-
if (key.endsWith('_id') && relationMappings[key]) {
|
|
375
|
-
const relationField = relationMappings[key];
|
|
376
|
-
const value = processedData[key];
|
|
377
|
-
|
|
378
|
-
// Remove the foreign key field
|
|
379
|
-
delete processedData[key];
|
|
380
|
-
|
|
381
|
-
// Add the relation field with proper Prisma syntax
|
|
382
|
-
if (value === null || value === undefined) {
|
|
383
|
-
processedData[relationField] = { disconnect: true };
|
|
384
|
-
} else {
|
|
385
|
-
processedData[relationField] = { connect: { id: value } };
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
return processedData;
|
|
319
|
+
public async count<T = any>(opModel: OpModelType<T>, where: { [k: string]: any } = {}): Promise<number> {
|
|
320
|
+
return await this.getCollectionHandler(opModel._collection).count({ where });
|
|
391
321
|
}
|
|
392
322
|
|
|
393
|
-
public getPrismaClient(): PrismaClient
|
|
394
|
-
|
|
395
|
-
if(!this.client || !this.connected){
|
|
323
|
+
public getPrismaClient(): PrismaClient {
|
|
324
|
+
if (!this.client || !this.connected) {
|
|
396
325
|
this.connectToDB();
|
|
397
326
|
}
|
|
398
327
|
|