@rws-framework/db 3.0.1 → 3.2.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/decorators/InverseRelation.js +30 -2
- package/dist/decorators/Relation.js +2 -1
- package/dist/helper/DbHelper.d.ts +1 -0
- package/dist/helper/DbHelper.js +6 -0
- package/dist/helper/db/schema-generator.js +29 -10
- package/dist/helper/db/type-converter.js +27 -6
- package/dist/helper/db/utils.d.ts +1 -1
- package/dist/helper/db/utils.js +11 -13
- package/dist/models/core/RWSModel.d.ts +4 -2
- package/dist/models/core/RWSModel.js +5 -67
- package/dist/models/interfaces/IDbOpts.d.ts +3 -0
- package/dist/models/utils/HydrateUtils.d.ts +13 -0
- package/dist/models/utils/HydrateUtils.js +80 -0
- package/exec/console.js +0 -2
- package/package.json +1 -1
- package/src/decorators/InverseRelation.ts +43 -2
- package/src/decorators/Relation.ts +4 -2
- package/src/helper/DbHelper.ts +9 -0
- package/src/helper/db/schema-generator.ts +41 -13
- package/src/helper/db/type-converter.ts +31 -4
- package/src/helper/db/utils.ts +15 -15
- package/src/models/core/RWSModel.ts +11 -86
- package/src/models/interfaces/IDbOpts.ts +3 -0
- package/src/models/utils/HydrateUtils.ts +100 -0
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
require("reflect-metadata");
|
|
4
|
+
const ModelUtils_1 = require("../models/utils/ModelUtils");
|
|
5
|
+
function guessForeignKey(inversionModel, bindingModel, decoratorsData) {
|
|
6
|
+
var _a;
|
|
7
|
+
let key = null;
|
|
8
|
+
let defaultKey = `${bindingModel._collection}_id`;
|
|
9
|
+
const relDecorators = {};
|
|
10
|
+
const trackDecorators = {};
|
|
11
|
+
if (Object.keys(trackDecorators).includes(key)) {
|
|
12
|
+
return key;
|
|
13
|
+
}
|
|
14
|
+
for (const decKey of Object.keys(decoratorsData)) {
|
|
15
|
+
const dec = decoratorsData[decKey];
|
|
16
|
+
if (dec.annotationType === 'Relation') {
|
|
17
|
+
relDecorators[decKey] = dec;
|
|
18
|
+
}
|
|
19
|
+
if (dec.annotationType === 'TrackType') {
|
|
20
|
+
trackDecorators[decKey] = dec;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const relKey of Object.keys(relDecorators)) {
|
|
24
|
+
const prodMeta = (_a = relDecorators[relKey]) === null || _a === void 0 ? void 0 : _a.metadata;
|
|
25
|
+
if (prodMeta && prodMeta.relatedTo._collection === bindingModel._collection) {
|
|
26
|
+
return prodMeta.relationField;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
4
31
|
function InverseRelation(inversionModel, sourceModel, relationOptions = null) {
|
|
5
32
|
return function (target, key) {
|
|
6
|
-
const metadataPromise = Promise.resolve().then(() => {
|
|
33
|
+
const metadataPromise = Promise.resolve().then(async () => {
|
|
7
34
|
const model = inversionModel();
|
|
8
35
|
const source = sourceModel();
|
|
36
|
+
const decoratorsData = await ModelUtils_1.ModelUtils.getModelAnnotations(model);
|
|
9
37
|
const metaOpts = {
|
|
10
38
|
...relationOptions,
|
|
11
39
|
key,
|
|
12
40
|
inversionModel: model,
|
|
13
|
-
foreignKey: relationOptions && relationOptions.foreignKey ? relationOptions.foreignKey :
|
|
41
|
+
foreignKey: relationOptions && relationOptions.foreignKey ? relationOptions.foreignKey : guessForeignKey(model, source, decoratorsData),
|
|
14
42
|
// Generate a unique relation name if one is not provided
|
|
15
43
|
relationName: relationOptions && relationOptions.relationName ?
|
|
16
44
|
relationOptions.relationName.toLowerCase() :
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
require("reflect-metadata");
|
|
4
|
-
const
|
|
4
|
+
const _DEFAULT_CASCADE = { onDelete: 'SetNull', onUpdate: 'Cascade' };
|
|
5
|
+
const _DEFAULTS = { required: false, many: false, embed: false, cascade: null };
|
|
5
6
|
function Relation(theModel, relationOptions = _DEFAULTS) {
|
|
6
7
|
return function (target, key) {
|
|
7
8
|
// Store the promise in metadata immediately
|
|
@@ -27,6 +27,7 @@ export declare class DbHelper {
|
|
|
27
27
|
* @param leaveFile Whether to leave the schema file after generation
|
|
28
28
|
*/
|
|
29
29
|
static pushDBModels(configService: IDbConfigHandler, dbService: DBService, leaveFile?: boolean): Promise<void>;
|
|
30
|
+
static migrateDBModels(configService: IDbConfigHandler, dbService: DBService, leaveFile?: boolean): Promise<void>;
|
|
30
31
|
/**
|
|
31
32
|
* Generate model sections for the schema
|
|
32
33
|
* @param model The model to generate a section for
|
package/dist/helper/DbHelper.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DbHelper = void 0;
|
|
4
|
+
const console_1 = require("@rws-framework/console");
|
|
4
5
|
const db_1 = require("./db");
|
|
5
6
|
/**
|
|
6
7
|
* Database helper class
|
|
@@ -27,6 +28,11 @@ class DbHelper {
|
|
|
27
28
|
static async pushDBModels(configService, dbService, leaveFile = false) {
|
|
28
29
|
return db_1.SchemaGenerator.pushDBModels(configService, dbService, leaveFile);
|
|
29
30
|
}
|
|
31
|
+
static async migrateDBModels(configService, dbService, leaveFile = false) {
|
|
32
|
+
process.env = { ...process.env, [this.dbUrlVarName]: configService.get('db_url') };
|
|
33
|
+
const [_, schemaPath] = db_1.DbUtils.getSchemaDir();
|
|
34
|
+
await console_1.rwsShell.runCommand(`${db_1.DbUtils.detectInstaller()} prisma migrate dev --create-only --schema=${schemaPath}`, process.cwd());
|
|
35
|
+
}
|
|
30
36
|
/**
|
|
31
37
|
* Generate model sections for the schema
|
|
32
38
|
* @param model The model to generate a section for
|
|
@@ -11,7 +11,6 @@ const _model_1 = require("../../models/_model");
|
|
|
11
11
|
const utils_1 = require("./utils");
|
|
12
12
|
const type_converter_1 = require("./type-converter");
|
|
13
13
|
const relation_manager_1 = require("./relation-manager");
|
|
14
|
-
const log = console.log;
|
|
15
14
|
/**
|
|
16
15
|
* Handles Prisma schema generation
|
|
17
16
|
*/
|
|
@@ -47,7 +46,7 @@ datasource db {
|
|
|
47
46
|
const modelName = model._collection;
|
|
48
47
|
section += `model ${modelName} {\n`;
|
|
49
48
|
if (!model._NO_ID) {
|
|
50
|
-
section += `\t${utils_1.DbUtils.generateId(dbType, modelMetadatas
|
|
49
|
+
section += `\t${utils_1.DbUtils.generateId(dbType, modelMetadatas)}\n`;
|
|
51
50
|
}
|
|
52
51
|
for (const key in modelMetadatas) {
|
|
53
52
|
const modelMetadata = modelMetadatas[key].metadata;
|
|
@@ -81,12 +80,16 @@ datasource db {
|
|
|
81
80
|
const relationFieldName = modelMetadata.relationField ? modelMetadata.relationField : key.toLowerCase() + '_' + modelMetadata.relationField.toLowerCase();
|
|
82
81
|
const relatedToField = modelMetadata.relatedToField || 'id';
|
|
83
82
|
const bindingFieldExists = !!modelMetadatas[relationFieldName];
|
|
83
|
+
if (modelMetadata.required === false) {
|
|
84
|
+
requiredString = '?';
|
|
85
|
+
}
|
|
86
|
+
const cascadeStr = cascadeOpts.length ? `, ${cascadeOpts.join(', ')}` : '';
|
|
84
87
|
if (isMany) {
|
|
85
88
|
// Add an inverse field to the related model if it doesn't exist
|
|
86
|
-
section += `\t${key} ${relatedModel._collection}[] @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"
|
|
89
|
+
section += `\t${key} ${relatedModel._collection}[] @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"${cascadeStr})\n`;
|
|
87
90
|
}
|
|
88
91
|
else {
|
|
89
|
-
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"
|
|
92
|
+
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"${cascadeStr})\n`;
|
|
90
93
|
if (!bindingFieldExists) {
|
|
91
94
|
const relatedFieldMeta = relatedModelMetadatas[relatedToField];
|
|
92
95
|
if (!relatedFieldMeta.metadata.required) {
|
|
@@ -170,15 +173,31 @@ datasource db {
|
|
|
170
173
|
// Process any database-specific options from the metadata
|
|
171
174
|
const dbSpecificTags = type_converter_1.TypeConverter.processTypeOptions(trackMeta, dbType);
|
|
172
175
|
tags.push(...dbSpecificTags);
|
|
173
|
-
if (modelName === 'category_translation' && key === 'meta_keywords') {
|
|
174
|
-
console.log({ requiredString, trackMeta });
|
|
175
|
-
}
|
|
176
176
|
section += `\t${key} ${type_converter_1.TypeConverter.toConfigCase(trackMeta, dbType, key === 'id')}${requiredString} ${tags.join(' ')}\n`;
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
if (model._SUPER_TAGS.length) {
|
|
180
|
+
section += '\n';
|
|
181
|
+
}
|
|
179
182
|
for (const superTag of model._SUPER_TAGS) {
|
|
180
183
|
const mapStr = superTag.map ? `, map: "${superTag.map}"` : '';
|
|
181
|
-
|
|
184
|
+
const superFields = [];
|
|
185
|
+
for (const superField of superTag.fields) {
|
|
186
|
+
const fieldMetadata = modelMetadatas[superField]['metadata'];
|
|
187
|
+
let pushed = false;
|
|
188
|
+
if (fieldMetadata.dbOptions && fieldMetadata.dbOptions.mysql && fieldMetadata.dbOptions.mysql.useType) {
|
|
189
|
+
switch (fieldMetadata.dbOptions.mysql.useType) {
|
|
190
|
+
case 'db.LongText':
|
|
191
|
+
superFields.push(`${superField}(length: 255)`);
|
|
192
|
+
pushed = true;
|
|
193
|
+
break;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (!pushed) {
|
|
197
|
+
superFields.push(superField);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
section += `\t@@${superTag.tagType}([${superFields.join(', ')}]${mapStr})\n`;
|
|
182
201
|
}
|
|
183
202
|
section += '}\n';
|
|
184
203
|
return section;
|
|
@@ -221,7 +240,7 @@ datasource db {
|
|
|
221
240
|
for (const model of dbModels) {
|
|
222
241
|
const modelSection = await SchemaGenerator.generateModelSections(model, configService);
|
|
223
242
|
template += '\n\n' + modelSection;
|
|
224
|
-
log(chalk_1.default.green('[RWS]'), chalk_1.default.blue('Building DB Model'), model.name);
|
|
243
|
+
console.log(chalk_1.default.green('[RWS]'), chalk_1.default.blue('Building DB Model'), model.name);
|
|
225
244
|
}
|
|
226
245
|
const [schemaDir, schemaPath] = utils_1.DbUtils.getSchemaDir();
|
|
227
246
|
if (!fs_1.default.existsSync(schemaDir)) {
|
|
@@ -233,7 +252,7 @@ datasource db {
|
|
|
233
252
|
fs_1.default.writeFileSync(schemaPath, template);
|
|
234
253
|
await console_1.rwsShell.runCommand(`${utils_1.DbUtils.detectInstaller()} prisma generate --schema=${schemaPath}`, process.cwd());
|
|
235
254
|
leaveFile = false;
|
|
236
|
-
log(chalk_1.default.green('[RWS Init]') + ' prisma schema generated from ', schemaPath);
|
|
255
|
+
console.log(chalk_1.default.green('[RWS Init]') + ' prisma schema generated from ', schemaPath);
|
|
237
256
|
if (!leaveFile) {
|
|
238
257
|
// fs.unlinkSync(schemaPath);
|
|
239
258
|
}
|
|
@@ -13,7 +13,22 @@ class TypeConverter {
|
|
|
13
13
|
let input = type.name;
|
|
14
14
|
// Handle basic types
|
|
15
15
|
if (input == 'Number') {
|
|
16
|
-
|
|
16
|
+
let numberOverride = false;
|
|
17
|
+
if (modelType.dbOptions && modelType.dbOptions.mysql) {
|
|
18
|
+
if (modelType.dbOptions.mysql.useType) {
|
|
19
|
+
if (['db.Float'].includes(modelType.dbOptions.mysql.useType)) {
|
|
20
|
+
input = 'Float';
|
|
21
|
+
numberOverride = true;
|
|
22
|
+
}
|
|
23
|
+
if (['db.Decimal'].includes(modelType.dbOptions.mysql.useType)) {
|
|
24
|
+
input = 'Decimal';
|
|
25
|
+
numberOverride = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!numberOverride) {
|
|
30
|
+
input = 'Int';
|
|
31
|
+
}
|
|
17
32
|
}
|
|
18
33
|
if (input == 'Object') {
|
|
19
34
|
input = 'Json';
|
|
@@ -66,20 +81,26 @@ class TypeConverter {
|
|
|
66
81
|
* @returns Array of tags to apply to the field
|
|
67
82
|
*/
|
|
68
83
|
static processTypeOptions(metadata, dbType) {
|
|
69
|
-
var _a, _b;
|
|
84
|
+
var _a, _b, _c, _d;
|
|
70
85
|
const tags = [...(metadata.tags || [])];
|
|
71
86
|
// Extract any database-specific options from the metadata
|
|
72
87
|
// and convert them to appropriate Prisma schema tags
|
|
73
88
|
if (metadata.dbOptions) {
|
|
74
89
|
// Handle MySQL-specific options
|
|
75
90
|
if (dbType === 'mysql' && metadata.dbOptions.mysql) {
|
|
91
|
+
let tag = null;
|
|
92
|
+
if (metadata.dbOptions.mysql.useType && !metadata.dbOptions.mysql.useText) {
|
|
93
|
+
const tagName = metadata.dbOptions.mysql.useType === 'VarChar' ? 'db.' + metadata.dbOptions.mysql.useType : metadata.dbOptions.mysql.useType;
|
|
94
|
+
let tagParams = tagName === 'db.VarChar' && metadata.dbOptions.mysql.maxLength ? metadata.dbOptions.mysql.maxLength : (((_b = (_a = metadata.dbOptions.mysql) === null || _a === void 0 ? void 0 : _a.params) === null || _b === void 0 ? void 0 : _b.join(', ')) || '');
|
|
95
|
+
tag = `@${tagName}(${tagParams})`;
|
|
96
|
+
}
|
|
76
97
|
if (metadata.dbOptions.mysql.useText) {
|
|
77
98
|
tags.push('@db.Text');
|
|
78
99
|
}
|
|
79
|
-
|
|
80
|
-
tags.push(
|
|
100
|
+
if (tag) {
|
|
101
|
+
tags.push(tag);
|
|
81
102
|
}
|
|
82
|
-
if (metadata.dbOptions.mysql.useUuid && ((
|
|
103
|
+
if (metadata.dbOptions.mysql.useUuid && ((_c = metadata.tags) === null || _c === void 0 ? void 0 : _c.includes('id'))) {
|
|
83
104
|
tags.push('default(uuid())');
|
|
84
105
|
}
|
|
85
106
|
}
|
|
@@ -88,7 +109,7 @@ class TypeConverter {
|
|
|
88
109
|
if (metadata.dbOptions.postgres.useText) {
|
|
89
110
|
tags.push('@db.Text');
|
|
90
111
|
}
|
|
91
|
-
if (metadata.dbOptions.postgres.useUuid && ((
|
|
112
|
+
if (metadata.dbOptions.postgres.useUuid && ((_d = metadata.tags) === null || _d === void 0 ? void 0 : _d.includes('id'))) {
|
|
92
113
|
tags.push('@default(uuid())');
|
|
93
114
|
tags.push('@db.Uuid');
|
|
94
115
|
}
|
|
@@ -18,7 +18,7 @@ export declare class DbUtils {
|
|
|
18
18
|
static generateId(dbType: IDbConfigParams['db_type'], modelMeta: Record<string, {
|
|
19
19
|
annotationType: string;
|
|
20
20
|
metadata: IIdMetaOpts;
|
|
21
|
-
}>,
|
|
21
|
+
}>, optional?: boolean): string;
|
|
22
22
|
}
|
|
23
23
|
export declare const workspaceRootPath: string;
|
|
24
24
|
export declare const moduleDirPath: string;
|
package/dist/helper/db/utils.js
CHANGED
|
@@ -34,7 +34,7 @@ class DbUtils {
|
|
|
34
34
|
/**
|
|
35
35
|
* Generate an ID field based on the database type
|
|
36
36
|
*/
|
|
37
|
-
static generateId(dbType, modelMeta,
|
|
37
|
+
static generateId(dbType, modelMeta, optional = false) {
|
|
38
38
|
var _a, _b, _c, _d;
|
|
39
39
|
let useUuid = false;
|
|
40
40
|
let field = 'id';
|
|
@@ -46,9 +46,6 @@ class DbUtils {
|
|
|
46
46
|
if (annotationType == 'IdType') {
|
|
47
47
|
const dbSpecificTags = type_converter_1.TypeConverter.processTypeOptions({ tags: [], dbOptions: modelMetadata.dbOptions }, dbType);
|
|
48
48
|
tags.push(...dbSpecificTags);
|
|
49
|
-
if (debug) {
|
|
50
|
-
console.log({ modelMetadata: modelMetadata.dbOptions });
|
|
51
|
-
}
|
|
52
49
|
field = key;
|
|
53
50
|
if ((_b = (_a = modelMetadata.dbOptions) === null || _a === void 0 ? void 0 : _a.mysql) === null || _b === void 0 ? void 0 : _b.useUuid) {
|
|
54
51
|
useUuid = true;
|
|
@@ -63,23 +60,27 @@ class DbUtils {
|
|
|
63
60
|
}
|
|
64
61
|
}
|
|
65
62
|
let idString;
|
|
63
|
+
let reqStr = '';
|
|
64
|
+
if (optional) {
|
|
65
|
+
reqStr = '?';
|
|
66
|
+
}
|
|
66
67
|
switch (dbType) {
|
|
67
68
|
case 'mongodb':
|
|
68
|
-
idString = `${field} String @id @default(auto()) @map("_id") @db.ObjectId`;
|
|
69
|
+
idString = `${field} String${reqStr} @id @default(auto()) @map("_id") @db.ObjectId`;
|
|
69
70
|
break;
|
|
70
71
|
case 'mysql':
|
|
71
72
|
idString = useUuid
|
|
72
|
-
? `${field} String @id @default(uuid())`
|
|
73
|
-
: `${field} Int @id @default(autoincrement())`;
|
|
73
|
+
? `${field} String${reqStr} @id @default(uuid())`
|
|
74
|
+
: `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
74
75
|
break;
|
|
75
76
|
case 'postgresql':
|
|
76
77
|
case 'postgres':
|
|
77
78
|
idString = useUuid
|
|
78
|
-
? `${field} String @id @default(uuid())`
|
|
79
|
-
: `${field} Int @id @default(autoincrement())`;
|
|
79
|
+
? `${field} String${reqStr} @id @default(uuid())`
|
|
80
|
+
: `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
80
81
|
break;
|
|
81
82
|
case 'sqlite':
|
|
82
|
-
idString = `${field} Int @id @default(autoincrement())`;
|
|
83
|
+
idString = `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
83
84
|
break;
|
|
84
85
|
}
|
|
85
86
|
if (tags.length) {
|
|
@@ -88,9 +89,6 @@ class DbUtils {
|
|
|
88
89
|
if (!idString) {
|
|
89
90
|
throw new Error(`DB type "${dbType}" is not supported!`);
|
|
90
91
|
}
|
|
91
|
-
if (debug) {
|
|
92
|
-
console.log({ idString, useUuid });
|
|
93
|
-
}
|
|
94
92
|
return idString;
|
|
95
93
|
}
|
|
96
94
|
}
|
|
@@ -4,6 +4,8 @@ import { OpModelType } from '../interfaces/OpModelType';
|
|
|
4
4
|
import { FindByType, IPaginationParams } from '../../types/FindParams';
|
|
5
5
|
import { DBService } from '../../services/DBService';
|
|
6
6
|
import { ISuperTagData } from '../../decorators/RWSCollection';
|
|
7
|
+
import { RelManyMetaType, RelOneMetaType } from '../types/RelationTypes';
|
|
8
|
+
import { IRWSModel } from '../../types/IRWSModel';
|
|
7
9
|
declare class RWSModel<T> implements IModel {
|
|
8
10
|
static services: IRWSModelServices;
|
|
9
11
|
[key: string]: any;
|
|
@@ -30,9 +32,9 @@ declare class RWSModel<T> implements IModel {
|
|
|
30
32
|
_asyncFill(data: any, fullDataMode?: boolean, allowRelations?: boolean): Promise<T>;
|
|
31
33
|
private getModelScalarFields;
|
|
32
34
|
private getRelationOneMeta;
|
|
33
|
-
static getRelationOneMeta(model: any, classFields: string[]): Promise<
|
|
35
|
+
static getRelationOneMeta(model: any, classFields: string[]): Promise<RelOneMetaType<IRWSModel>>;
|
|
34
36
|
private getRelationManyMeta;
|
|
35
|
-
static getRelationManyMeta(model: any, classFields: string[]): Promise<
|
|
37
|
+
static getRelationManyMeta(model: any, classFields: string[]): Promise<RelManyMetaType<IRWSModel>>;
|
|
36
38
|
static paginate<T extends RWSModel<T>>(this: OpModelType<T>, paginateParams: IPaginationParams, findParams?: FindByType): Promise<T[]>;
|
|
37
39
|
toMongo(): Promise<any>;
|
|
38
40
|
getCollection(): string | null;
|
|
@@ -15,6 +15,7 @@ const FieldsHelper_1 = require("../../helper/FieldsHelper");
|
|
|
15
15
|
const RelationUtils_1 = require("../utils/RelationUtils");
|
|
16
16
|
const TimeSeriesUtils_1 = require("../utils/TimeSeriesUtils");
|
|
17
17
|
const ModelUtils_1 = require("../utils/ModelUtils");
|
|
18
|
+
const HydrateUtils_1 = require("../utils/HydrateUtils");
|
|
18
19
|
class RWSModel {
|
|
19
20
|
constructor(data = null) {
|
|
20
21
|
if (!this.getCollection()) {
|
|
@@ -76,7 +77,6 @@ class RWSModel {
|
|
|
76
77
|
}
|
|
77
78
|
async _asyncFill(data, fullDataMode = false, allowRelations = true) {
|
|
78
79
|
const collections_to_models = {};
|
|
79
|
-
const timeSeriesIds = TimeSeriesUtils_1.TimeSeriesUtils.getTimeSeriesModelFields(this);
|
|
80
80
|
const classFields = FieldsHelper_1.FieldsHelper.getAllClassFields(this.constructor);
|
|
81
81
|
// Get both relation metadata types asynchronously
|
|
82
82
|
const [relOneData, relManyData] = await Promise.all([
|
|
@@ -88,73 +88,10 @@ class RWSModel {
|
|
|
88
88
|
});
|
|
89
89
|
const seriesHydrationfields = [];
|
|
90
90
|
if (allowRelations) {
|
|
91
|
-
|
|
92
|
-
for (const key in relManyData) {
|
|
93
|
-
if (!fullDataMode && this.constructor._CUT_KEYS.includes(key)) {
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
const relMeta = relManyData[key];
|
|
97
|
-
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(this, relMeta.key);
|
|
98
|
-
if (relationEnabled) {
|
|
99
|
-
this[relMeta.key] = await relMeta.inversionModel.findBy({
|
|
100
|
-
conditions: {
|
|
101
|
-
[relMeta.foreignKey]: data.id
|
|
102
|
-
},
|
|
103
|
-
allowRelations: false
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
// Handle one-to-one relations
|
|
108
|
-
for (const key in relOneData) {
|
|
109
|
-
if (!fullDataMode && this.constructor._CUT_KEYS.includes(key)) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const relMeta = relOneData[key];
|
|
113
|
-
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(this, relMeta.key);
|
|
114
|
-
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
115
|
-
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`);
|
|
116
|
-
}
|
|
117
|
-
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
118
|
-
this[relMeta.key] = await relMeta.model.find(data[relMeta.hydrationField], { allowRelations: false });
|
|
119
|
-
}
|
|
120
|
-
else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
121
|
-
const newRelModel = await relMeta.model.create(data[relMeta.key]);
|
|
122
|
-
this[relMeta.key] = await newRelModel.save();
|
|
123
|
-
}
|
|
124
|
-
const cutKeys = this.constructor._CUT_KEYS;
|
|
125
|
-
const trackedField = Object.keys((await ModelUtils_1.ModelUtils.getModelAnnotations(this.constructor))).includes(relMeta.hydrationField);
|
|
126
|
-
if (!cutKeys.includes(relMeta.hydrationField) && !trackedField) {
|
|
127
|
-
cutKeys.push(relMeta.hydrationField);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
91
|
+
await HydrateUtils_1.HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
130
92
|
}
|
|
131
93
|
// Process regular fields and time series
|
|
132
|
-
|
|
133
|
-
if (data.hasOwnProperty(key)) {
|
|
134
|
-
if (!fullDataMode && this.constructor._CUT_KEYS.includes(key)) {
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (Object.keys(relOneData).includes(key)) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
if (seriesHydrationfields.includes(key)) {
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
const timeSeriesMetaData = timeSeriesIds[key];
|
|
144
|
-
if (timeSeriesMetaData) {
|
|
145
|
-
this[key] = data[key];
|
|
146
|
-
const seriesModel = collections_to_models[timeSeriesMetaData.collection];
|
|
147
|
-
const dataModels = await seriesModel.findBy({
|
|
148
|
-
id: { in: data[key] }
|
|
149
|
-
});
|
|
150
|
-
seriesHydrationfields.push(timeSeriesMetaData.hydrationField);
|
|
151
|
-
this[timeSeriesMetaData.hydrationField] = dataModels;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
this[key] = data[key];
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
94
|
+
await HydrateUtils_1.HydrateUtils.hydrateDataFields(this, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
158
95
|
return this;
|
|
159
96
|
}
|
|
160
97
|
getModelScalarFields(model) {
|
|
@@ -334,7 +271,8 @@ class RWSModel {
|
|
|
334
271
|
const collection = Reflect.get(this, '_collection');
|
|
335
272
|
this.checkForInclusionWithThrow(this.name);
|
|
336
273
|
try {
|
|
337
|
-
const
|
|
274
|
+
const paginateParams = (findParams === null || findParams === void 0 ? void 0 : findParams.pagination) ? findParams === null || findParams === void 0 ? void 0 : findParams.pagination : undefined;
|
|
275
|
+
const dbData = await this.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
|
|
338
276
|
if (dbData.length) {
|
|
339
277
|
const instanced = [];
|
|
340
278
|
for (const data of dbData) {
|
|
@@ -5,13 +5,16 @@ export interface IDbOpts {
|
|
|
5
5
|
useText?: boolean;
|
|
6
6
|
maxLength?: number;
|
|
7
7
|
useUuid?: boolean;
|
|
8
|
+
params?: string[];
|
|
8
9
|
};
|
|
9
10
|
postgres?: {
|
|
10
11
|
useText?: boolean;
|
|
11
12
|
useUuid?: boolean;
|
|
13
|
+
params?: string[];
|
|
12
14
|
};
|
|
13
15
|
mongodb?: {
|
|
14
16
|
customType?: string;
|
|
17
|
+
params?: string[];
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RWSModel } from "../core/RWSModel";
|
|
2
|
+
import { RelManyMetaType, RelOneMetaType } from "../types/RelationTypes";
|
|
3
|
+
import { IRWSModel } from "../../types/IRWSModel";
|
|
4
|
+
export declare class HydrateUtils {
|
|
5
|
+
static hydrateDataFields(model: RWSModel<any>, collections_to_models: {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
static hydrateRelations(model: RWSModel<any>, relManyData: RelManyMetaType<IRWSModel>, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HydrateUtils = void 0;
|
|
4
|
+
const TimeSeriesUtils_1 = require("./TimeSeriesUtils");
|
|
5
|
+
const RelationUtils_1 = require("./RelationUtils");
|
|
6
|
+
const ModelUtils_1 = require("./ModelUtils");
|
|
7
|
+
class HydrateUtils {
|
|
8
|
+
static async hydrateDataFields(model, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data) {
|
|
9
|
+
const timeSeriesIds = TimeSeriesUtils_1.TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
10
|
+
for (const key in data) {
|
|
11
|
+
if (data.hasOwnProperty(key)) {
|
|
12
|
+
if (!fullDataMode && (model).constructor._CUT_KEYS.includes(key)) {
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
if (Object.keys(relOneData).includes(key)) {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (seriesHydrationfields.includes(key)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const timeSeriesMetaData = timeSeriesIds[key];
|
|
22
|
+
if (timeSeriesMetaData) {
|
|
23
|
+
model[key] = data[key];
|
|
24
|
+
const seriesModel = collections_to_models[timeSeriesMetaData.collection];
|
|
25
|
+
const dataModels = await seriesModel.findBy({
|
|
26
|
+
id: { in: data[key] }
|
|
27
|
+
});
|
|
28
|
+
seriesHydrationfields.push(timeSeriesMetaData.hydrationField);
|
|
29
|
+
model[timeSeriesMetaData.hydrationField] = dataModels;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
model[key] = data[key];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
static async hydrateRelations(model, relManyData, relOneData, seriesHydrationfields, fullDataMode, data) {
|
|
38
|
+
// Handle many-to-many relations
|
|
39
|
+
for (const key in relManyData) {
|
|
40
|
+
if (!fullDataMode && model.constructor._CUT_KEYS.includes(key)) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const relMeta = relManyData[key];
|
|
44
|
+
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
45
|
+
if (relationEnabled) {
|
|
46
|
+
model[relMeta.key] = await relMeta.inversionModel.findBy({
|
|
47
|
+
conditions: {
|
|
48
|
+
[relMeta.foreignKey]: data.id
|
|
49
|
+
},
|
|
50
|
+
allowRelations: false
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Handle one-to-one relations
|
|
55
|
+
for (const key in relOneData) {
|
|
56
|
+
if (!fullDataMode && model.constructor._CUT_KEYS.includes(key)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const relMeta = relOneData[key];
|
|
60
|
+
const relationEnabled = !RelationUtils_1.RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
61
|
+
if (!data[relMeta.hydrationField] && relMeta.required) {
|
|
62
|
+
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`);
|
|
63
|
+
}
|
|
64
|
+
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
65
|
+
model[relMeta.key] = await relMeta.model.findOneBy({ conditions: { [relMeta.foreignKey]: data[relMeta.hydrationField] } }, { allowRelations: false });
|
|
66
|
+
}
|
|
67
|
+
else if (relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]) {
|
|
68
|
+
const newRelModel = await relMeta.model.create(data[relMeta.key]);
|
|
69
|
+
model[relMeta.key] = await newRelModel.save();
|
|
70
|
+
}
|
|
71
|
+
const cutKeys = model.constructor._CUT_KEYS;
|
|
72
|
+
const trackedField = Object.keys((await ModelUtils_1.ModelUtils.getModelAnnotations(model.constructor))).includes(relMeta.hydrationField);
|
|
73
|
+
if (!cutKeys.includes(relMeta.hydrationField) && !trackedField) {
|
|
74
|
+
cutKeys.push(relMeta.hydrationField);
|
|
75
|
+
}
|
|
76
|
+
// seriesHydrationfields.push(relMeta.hydrationField);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.HydrateUtils = HydrateUtils;
|
package/exec/console.js
CHANGED
|
@@ -15,8 +15,6 @@ const getCachedPath = (key) => path.resolve(rwsCliConfigDir, key);
|
|
|
15
15
|
|
|
16
16
|
const currentCwd = path.resolve(__dirname);
|
|
17
17
|
|
|
18
|
-
console.log({params})
|
|
19
|
-
|
|
20
18
|
|
|
21
19
|
const commandString = `npx webpack --config db.rws.webpack.config.js --output-path ./build ${process.cwd()} ${params[2]}`;
|
|
22
20
|
function needsCacheWarming(){
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
2
|
import { RWSModel, OpModelType } from '../models/_model';
|
|
3
|
+
import { ModelUtils } from '../models/utils/ModelUtils';
|
|
3
4
|
|
|
4
5
|
export interface InverseRelationOpts {
|
|
5
6
|
key: string,
|
|
@@ -10,17 +11,57 @@ export interface InverseRelationOpts {
|
|
|
10
11
|
mappingName?: string
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
function guessForeignKey(inversionModel: OpModelType<RWSModel<any>>, bindingModel: OpModelType<RWSModel<any>>, decoratorsData: any)
|
|
15
|
+
{
|
|
16
|
+
let key: string | null = null;
|
|
17
|
+
let defaultKey = `${bindingModel._collection}_id`;
|
|
18
|
+
|
|
19
|
+
const relDecorators: Record<string, {
|
|
20
|
+
annotationType: string;
|
|
21
|
+
metadata: any;
|
|
22
|
+
}> = {};
|
|
23
|
+
|
|
24
|
+
const trackDecorators: Record<string, {
|
|
25
|
+
annotationType: string;
|
|
26
|
+
metadata: any;
|
|
27
|
+
}> = {};
|
|
28
|
+
|
|
29
|
+
if(Object.keys(trackDecorators).includes(key)){
|
|
30
|
+
return key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
for(const decKey of Object.keys(decoratorsData)){
|
|
34
|
+
const dec = decoratorsData[decKey];
|
|
35
|
+
if(dec.annotationType === 'Relation'){
|
|
36
|
+
relDecorators[decKey] = dec;
|
|
37
|
+
}
|
|
38
|
+
if(dec.annotationType === 'TrackType'){
|
|
39
|
+
trackDecorators[decKey] = dec;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for(const relKey of Object.keys(relDecorators)){
|
|
44
|
+
const prodMeta = relDecorators[relKey]?.metadata;
|
|
45
|
+
if(prodMeta && prodMeta.relatedTo._collection === bindingModel._collection){
|
|
46
|
+
return prodMeta.relationField;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
|
|
13
53
|
function InverseRelation(inversionModel: () => OpModelType<RWSModel<any>>, sourceModel: () => OpModelType<RWSModel<any>>, relationOptions: Partial<InverseRelationOpts> = null) {
|
|
14
54
|
return function (target: any, key: string) {
|
|
15
|
-
const metadataPromise = Promise.resolve().then(() => {
|
|
55
|
+
const metadataPromise = Promise.resolve().then(async () => {
|
|
16
56
|
const model = inversionModel();
|
|
17
57
|
const source = sourceModel();
|
|
58
|
+
const decoratorsData = await ModelUtils.getModelAnnotations(model);
|
|
18
59
|
|
|
19
60
|
const metaOpts: InverseRelationOpts = {
|
|
20
61
|
...relationOptions,
|
|
21
62
|
key,
|
|
22
63
|
inversionModel: model,
|
|
23
|
-
foreignKey: relationOptions && relationOptions.foreignKey ? relationOptions.foreignKey :
|
|
64
|
+
foreignKey: relationOptions && relationOptions.foreignKey ? relationOptions.foreignKey : guessForeignKey(model, source, decoratorsData),
|
|
24
65
|
// Generate a unique relation name if one is not provided
|
|
25
66
|
relationName: relationOptions && relationOptions.relationName ?
|
|
26
67
|
relationOptions.relationName.toLowerCase() :
|
|
@@ -20,13 +20,15 @@ export interface IRelationOpts {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const _DEFAULT_CASCADE = { onDelete: 'SetNull', onUpdate: 'Cascade' };
|
|
24
|
+
|
|
25
|
+
const _DEFAULTS: Partial<IRelationOpts> = { required: false, many: false, embed: false, cascade: null};
|
|
24
26
|
|
|
25
27
|
function Relation(theModel: () => OpModelType<RWSModel<any>>, relationOptions: Partial<IRelationOpts> = _DEFAULTS) {
|
|
26
28
|
return function(target: any, key: string) {
|
|
27
29
|
// Store the promise in metadata immediately
|
|
28
30
|
|
|
29
|
-
const metadataPromise = Promise.resolve().then(() => {
|
|
31
|
+
const metadataPromise = Promise.resolve().then(() => {
|
|
30
32
|
const relatedTo = theModel();
|
|
31
33
|
|
|
32
34
|
const metaOpts: IRelationOpts = {
|
package/src/helper/DbHelper.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IDbConfigHandler, IDbConfigParams, IdGeneratorOptions } from '../types/DbConfigHandler';
|
|
2
2
|
import { OpModelType } from '../models/_model';
|
|
3
3
|
import { DBService } from '../services/DBService';
|
|
4
|
+
import { rwsShell } from '@rws-framework/console';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
DbUtils,
|
|
@@ -41,6 +42,14 @@ export class DbHelper {
|
|
|
41
42
|
static async pushDBModels(configService: IDbConfigHandler, dbService: DBService, leaveFile = false): Promise<void> {
|
|
42
43
|
return SchemaGenerator.pushDBModels(configService, dbService, leaveFile);
|
|
43
44
|
}
|
|
45
|
+
|
|
46
|
+
static async migrateDBModels(configService: IDbConfigHandler, dbService: DBService, leaveFile = false): Promise<void> {
|
|
47
|
+
process.env = { ...process.env, [this.dbUrlVarName]: configService.get('db_url') };
|
|
48
|
+
|
|
49
|
+
const [_, schemaPath] = DbUtils.getSchemaDir();
|
|
50
|
+
|
|
51
|
+
await rwsShell.runCommand(`${DbUtils.detectInstaller()} prisma migrate dev --create-only --schema=${schemaPath}`, process.cwd());
|
|
52
|
+
}
|
|
44
53
|
|
|
45
54
|
/**
|
|
46
55
|
* Generate model sections for the schema
|
|
@@ -15,7 +15,6 @@ import { RelationManager } from './relation-manager';
|
|
|
15
15
|
import { ITrackerMetaOpts } from '../../decorators/TrackType';
|
|
16
16
|
import { IDbOpts } from '../../models/interfaces/IDbOpts';
|
|
17
17
|
|
|
18
|
-
const log = console.log;
|
|
19
18
|
|
|
20
19
|
/**
|
|
21
20
|
* Handles Prisma schema generation
|
|
@@ -62,7 +61,7 @@ datasource db {
|
|
|
62
61
|
if(
|
|
63
62
|
!model._NO_ID
|
|
64
63
|
){
|
|
65
|
-
section += `\t${DbUtils.generateId(dbType, modelMetadatas
|
|
64
|
+
section += `\t${DbUtils.generateId(dbType, modelMetadatas)}\n`;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
67
|
for (const key in modelMetadatas) {
|
|
@@ -108,13 +107,19 @@ datasource db {
|
|
|
108
107
|
const relationFieldName = modelMetadata.relationField ? modelMetadata.relationField : key.toLowerCase() + '_' + modelMetadata.relationField.toLowerCase();
|
|
109
108
|
|
|
110
109
|
const relatedToField = modelMetadata.relatedToField || 'id';
|
|
111
|
-
const bindingFieldExists = !!modelMetadatas[relationFieldName];
|
|
110
|
+
const bindingFieldExists = !!modelMetadatas[relationFieldName];
|
|
111
|
+
|
|
112
|
+
if(modelMetadata.required === false){
|
|
113
|
+
requiredString = '?';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const cascadeStr = cascadeOpts.length ? `, ${cascadeOpts.join(', ')}` : '' ;
|
|
112
117
|
|
|
113
118
|
if (isMany) {
|
|
114
119
|
// Add an inverse field to the related model if it doesn't exist
|
|
115
|
-
section += `\t${key} ${relatedModel._collection}[] @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"
|
|
120
|
+
section += `\t${key} ${relatedModel._collection}[] @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"${cascadeStr})\n`;
|
|
116
121
|
} else {
|
|
117
|
-
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"
|
|
122
|
+
section += `\t${key} ${relatedModel._collection}${requiredString} @relation("${relationName}", fields: [${relationFieldName}], references: [${relatedToField}], map: "${mapName}"${cascadeStr})\n`;
|
|
118
123
|
if(!bindingFieldExists){
|
|
119
124
|
const relatedFieldMeta = relatedModelMetadatas[relatedToField];
|
|
120
125
|
|
|
@@ -200,24 +205,47 @@ datasource db {
|
|
|
200
205
|
if(model._SUPER_TAGS.some(tag => tag.tagType === 'id' && tag.fields.includes(key))){
|
|
201
206
|
requiredString = '';
|
|
202
207
|
}
|
|
203
|
-
|
|
208
|
+
|
|
204
209
|
// Process any database-specific options from the metadata
|
|
205
210
|
const dbSpecificTags = TypeConverter.processTypeOptions(trackMeta as { tags: string[], dbOptions: IDbOpts['dbOptions'] }, dbType);
|
|
206
211
|
tags.push(...dbSpecificTags);
|
|
207
|
-
|
|
208
|
-
if(modelName === 'category_translation' && key === 'meta_keywords'){
|
|
209
|
-
console.log({requiredString, trackMeta});
|
|
210
|
-
}
|
|
212
|
+
|
|
211
213
|
|
|
212
214
|
section += `\t${key} ${TypeConverter.toConfigCase(trackMeta, dbType, key === 'id')}${requiredString} ${tags.join(' ')}\n`;
|
|
213
215
|
}
|
|
214
216
|
}
|
|
215
217
|
|
|
218
|
+
if(model._SUPER_TAGS.length){
|
|
219
|
+
section += '\n';
|
|
220
|
+
}
|
|
221
|
+
|
|
216
222
|
for(const superTag of model._SUPER_TAGS){
|
|
217
223
|
|
|
218
224
|
const mapStr = superTag.map ? `, map: "${superTag.map}"` : '';
|
|
219
225
|
|
|
220
|
-
|
|
226
|
+
const superFields = [];
|
|
227
|
+
|
|
228
|
+
for(const superField of superTag.fields){
|
|
229
|
+
const fieldMetadata = modelMetadatas[superField]['metadata'];
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
let pushed = false;
|
|
233
|
+
|
|
234
|
+
if(fieldMetadata.dbOptions && fieldMetadata.dbOptions.mysql && fieldMetadata.dbOptions.mysql.useType){
|
|
235
|
+
switch(fieldMetadata.dbOptions.mysql.useType){
|
|
236
|
+
case 'db.LongText':
|
|
237
|
+
superFields.push(`${superField}(length: 255)`);
|
|
238
|
+
pushed = true;
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if(!pushed){
|
|
244
|
+
superFields.push(superField);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
section += `\t@@${superTag.tagType}([${superFields.join(', ')}]${mapStr})\n`;
|
|
221
249
|
}
|
|
222
250
|
|
|
223
251
|
section += '}\n';
|
|
@@ -276,7 +304,7 @@ datasource db {
|
|
|
276
304
|
|
|
277
305
|
template += '\n\n' + modelSection;
|
|
278
306
|
|
|
279
|
-
log(chalk.green('[RWS]'), chalk.blue('Building DB Model'), model.name);
|
|
307
|
+
console.log(chalk.green('[RWS]'), chalk.blue('Building DB Model'), model.name);
|
|
280
308
|
}
|
|
281
309
|
|
|
282
310
|
const [schemaDir, schemaPath] = DbUtils.getSchemaDir();
|
|
@@ -294,7 +322,7 @@ datasource db {
|
|
|
294
322
|
await rwsShell.runCommand(`${DbUtils.detectInstaller()} prisma generate --schema=${schemaPath}`, process.cwd());
|
|
295
323
|
|
|
296
324
|
leaveFile = false;
|
|
297
|
-
log(chalk.green('[RWS Init]') + ' prisma schema generated from ', schemaPath);
|
|
325
|
+
console.log(chalk.green('[RWS Init]') + ' prisma schema generated from ', schemaPath);
|
|
298
326
|
|
|
299
327
|
if (!leaveFile) {
|
|
300
328
|
// fs.unlinkSync(schemaPath);
|
|
@@ -16,7 +16,24 @@ export class TypeConverter {
|
|
|
16
16
|
|
|
17
17
|
// Handle basic types
|
|
18
18
|
if (input == 'Number') {
|
|
19
|
-
|
|
19
|
+
let numberOverride = false;
|
|
20
|
+
if(modelType.dbOptions && modelType.dbOptions.mysql){
|
|
21
|
+
if(modelType.dbOptions.mysql.useType){
|
|
22
|
+
if(['db.Float'].includes(modelType.dbOptions.mysql.useType)){
|
|
23
|
+
input = 'Float';
|
|
24
|
+
numberOverride = true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if(['db.Decimal'].includes(modelType.dbOptions.mysql.useType)){
|
|
28
|
+
input = 'Decimal';
|
|
29
|
+
numberOverride = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if(!numberOverride){
|
|
35
|
+
input = 'Int';
|
|
36
|
+
}
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
if (input == 'Object') {
|
|
@@ -64,7 +81,7 @@ export class TypeConverter {
|
|
|
64
81
|
} else {
|
|
65
82
|
resultField += '[]';
|
|
66
83
|
}
|
|
67
|
-
}
|
|
84
|
+
}
|
|
68
85
|
|
|
69
86
|
return resultField;
|
|
70
87
|
}
|
|
@@ -83,10 +100,20 @@ export class TypeConverter {
|
|
|
83
100
|
if (metadata.dbOptions) {
|
|
84
101
|
// Handle MySQL-specific options
|
|
85
102
|
if (dbType === 'mysql' && metadata.dbOptions.mysql) {
|
|
103
|
+
let tag = null;
|
|
104
|
+
|
|
105
|
+
if (metadata.dbOptions.mysql.useType && !metadata.dbOptions.mysql.useText) {
|
|
106
|
+
const tagName = metadata.dbOptions.mysql.useType === 'VarChar' ? 'db.' + metadata.dbOptions.mysql.useType : metadata.dbOptions.mysql.useType;
|
|
107
|
+
let tagParams = tagName === 'db.VarChar' && metadata.dbOptions.mysql.maxLength ? metadata.dbOptions.mysql.maxLength : (metadata.dbOptions.mysql?.params?.join(', ') || '');
|
|
108
|
+
tag = `@${tagName}(${tagParams})`;
|
|
109
|
+
}
|
|
110
|
+
|
|
86
111
|
if (metadata.dbOptions.mysql.useText) {
|
|
87
112
|
tags.push('@db.Text');
|
|
88
|
-
}
|
|
89
|
-
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if(tag){
|
|
116
|
+
tags.push(tag);
|
|
90
117
|
}
|
|
91
118
|
|
|
92
119
|
if (metadata.dbOptions.mysql.useUuid && metadata.tags?.includes('id')) {
|
package/src/helper/db/utils.ts
CHANGED
|
@@ -40,7 +40,7 @@ export class DbUtils {
|
|
|
40
40
|
static generateId(
|
|
41
41
|
dbType: IDbConfigParams['db_type'],
|
|
42
42
|
modelMeta: Record<string, { annotationType: string, metadata: IIdMetaOpts }>,
|
|
43
|
-
|
|
43
|
+
optional = false
|
|
44
44
|
): string {
|
|
45
45
|
let useUuid = false;
|
|
46
46
|
let field = 'id';
|
|
@@ -53,10 +53,7 @@ export class DbUtils {
|
|
|
53
53
|
if(key !== 'id'){
|
|
54
54
|
if(annotationType == 'IdType'){
|
|
55
55
|
const dbSpecificTags = TypeConverter.processTypeOptions({ tags: [], dbOptions: modelMetadata.dbOptions }, dbType);
|
|
56
|
-
tags.push(...dbSpecificTags);
|
|
57
|
-
if(debug){
|
|
58
|
-
console.log({modelMetadata: modelMetadata.dbOptions});
|
|
59
|
-
}
|
|
56
|
+
tags.push(...dbSpecificTags);
|
|
60
57
|
|
|
61
58
|
field = key;
|
|
62
59
|
|
|
@@ -76,29 +73,35 @@ export class DbUtils {
|
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
let idString: string;
|
|
76
|
+
|
|
77
|
+
let reqStr = '';
|
|
78
|
+
|
|
79
|
+
if(optional){
|
|
80
|
+
reqStr = '?';
|
|
81
|
+
}
|
|
79
82
|
|
|
80
83
|
switch (dbType) {
|
|
81
84
|
case 'mongodb':
|
|
82
|
-
idString = `${field} String @id @default(auto()) @map("_id") @db.ObjectId`;
|
|
85
|
+
idString = `${field} String${reqStr} @id @default(auto()) @map("_id") @db.ObjectId`;
|
|
83
86
|
break;
|
|
84
87
|
|
|
85
88
|
case 'mysql':
|
|
86
89
|
idString = useUuid
|
|
87
|
-
? `${field} String @id @default(uuid())`
|
|
88
|
-
: `${field} Int @id @default(autoincrement())`;
|
|
90
|
+
? `${field} String${reqStr} @id @default(uuid())`
|
|
91
|
+
: `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
89
92
|
break;
|
|
90
93
|
|
|
91
94
|
case 'postgresql':
|
|
92
95
|
case 'postgres':
|
|
93
96
|
idString = useUuid
|
|
94
|
-
? `${field} String @id @default(uuid())`
|
|
95
|
-
: `${field} Int @id @default(autoincrement())`;
|
|
97
|
+
? `${field} String${reqStr} @id @default(uuid())`
|
|
98
|
+
: `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
96
99
|
break;
|
|
97
100
|
|
|
98
101
|
case 'sqlite':
|
|
99
|
-
idString = `${field} Int @id @default(autoincrement())`;
|
|
102
|
+
idString = `${field} Int${reqStr} @id @default(autoincrement())`;
|
|
100
103
|
break;
|
|
101
|
-
}
|
|
104
|
+
}
|
|
102
105
|
|
|
103
106
|
if(tags.length){
|
|
104
107
|
idString += ' '+tags.join(' ');
|
|
@@ -108,9 +111,6 @@ export class DbUtils {
|
|
|
108
111
|
throw new Error(`DB type "${dbType}" is not supported!`);
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
if(debug){
|
|
112
|
-
console.log({idString, useUuid});
|
|
113
|
-
}
|
|
114
114
|
|
|
115
115
|
return idString;
|
|
116
116
|
}
|
|
@@ -11,6 +11,9 @@ import { ModelUtils } from '../utils/ModelUtils';
|
|
|
11
11
|
// import timeSeriesModel from './TimeSeriesModel';
|
|
12
12
|
import { DBService } from '../../services/DBService';
|
|
13
13
|
import { ISuperTagData } from '../../decorators/RWSCollection';
|
|
14
|
+
import { RelManyMetaType, RelOneMetaType } from '../types/RelationTypes';
|
|
15
|
+
import { IRWSModel } from '../../types/IRWSModel';
|
|
16
|
+
import { HydrateUtils } from '../utils/HydrateUtils';
|
|
14
17
|
|
|
15
18
|
class RWSModel<T> implements IModel {
|
|
16
19
|
static services: IRWSModelServices = {};
|
|
@@ -97,9 +100,7 @@ class RWSModel<T> implements IModel {
|
|
|
97
100
|
}
|
|
98
101
|
|
|
99
102
|
public async _asyncFill(data: any, fullDataMode = false, allowRelations = true): Promise<T> {
|
|
100
|
-
const collections_to_models: {[key: string]: any} = {};
|
|
101
|
-
const timeSeriesIds = TimeSeriesUtils.getTimeSeriesModelFields(this);
|
|
102
|
-
|
|
103
|
+
const collections_to_models: {[key: string]: any} = {};
|
|
103
104
|
const classFields = FieldsHelper.getAllClassFields(this.constructor);
|
|
104
105
|
|
|
105
106
|
// Get both relation metadata types asynchronously
|
|
@@ -116,93 +117,16 @@ class RWSModel<T> implements IModel {
|
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
if (allowRelations) {
|
|
119
|
-
|
|
120
|
-
for (const key in relManyData) {
|
|
121
|
-
if(!fullDataMode && (this as any).constructor._CUT_KEYS.includes(key)){
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const relMeta = relManyData[key];
|
|
126
|
-
|
|
127
|
-
const relationEnabled = !RelationUtils.checkRelDisabled(this, relMeta.key);
|
|
128
|
-
if (relationEnabled) {
|
|
129
|
-
this[relMeta.key] = await relMeta.inversionModel.findBy({
|
|
130
|
-
conditions: {
|
|
131
|
-
[relMeta.foreignKey]: data.id
|
|
132
|
-
},
|
|
133
|
-
allowRelations: false
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Handle one-to-one relations
|
|
139
|
-
for (const key in relOneData) {
|
|
140
|
-
if(!fullDataMode && (this as any).constructor._CUT_KEYS.includes(key)){
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const relMeta = relOneData[key];
|
|
145
|
-
const relationEnabled = !RelationUtils.checkRelDisabled(this, relMeta.key);
|
|
146
|
-
|
|
147
|
-
if(!data[relMeta.hydrationField] && relMeta.required){
|
|
148
|
-
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
152
|
-
this[relMeta.key] = await relMeta.model.find(data[relMeta.hydrationField], { allowRelations: false });
|
|
153
|
-
}
|
|
154
|
-
else if(relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]){
|
|
155
|
-
const newRelModel: RWSModel<any> = await relMeta.model.create(data[relMeta.key]);
|
|
156
|
-
this[relMeta.key] = await newRelModel.save();
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const cutKeys = ((this.constructor as any)._CUT_KEYS as string[]);
|
|
160
|
-
|
|
161
|
-
const trackedField = Object.keys((await ModelUtils.getModelAnnotations(this.constructor as any))).includes(relMeta.hydrationField);
|
|
162
|
-
|
|
163
|
-
if(!cutKeys.includes(relMeta.hydrationField) && !trackedField){
|
|
164
|
-
cutKeys.push(relMeta.hydrationField)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
120
|
+
await HydrateUtils.hydrateRelations(this, relManyData, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
167
121
|
}
|
|
168
122
|
|
|
169
123
|
// Process regular fields and time series
|
|
170
|
-
|
|
171
|
-
if (data.hasOwnProperty(key)) {
|
|
172
|
-
if(!fullDataMode && (this as any).constructor._CUT_KEYS.includes(key)){
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (Object.keys(relOneData).includes(key)) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (seriesHydrationfields.includes(key)) {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const timeSeriesMetaData = timeSeriesIds[key];
|
|
186
|
-
|
|
187
|
-
if (timeSeriesMetaData) {
|
|
188
|
-
this[key] = data[key];
|
|
189
|
-
const seriesModel = collections_to_models[timeSeriesMetaData.collection];
|
|
190
|
-
|
|
191
|
-
const dataModels = await seriesModel.findBy({
|
|
192
|
-
id: { in: data[key] }
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
seriesHydrationfields.push(timeSeriesMetaData.hydrationField);
|
|
196
|
-
|
|
197
|
-
this[timeSeriesMetaData.hydrationField] = dataModels;
|
|
198
|
-
} else {
|
|
199
|
-
this[key] = data[key];
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
124
|
+
await HydrateUtils.hydrateDataFields(this, collections_to_models, relOneData, seriesHydrationfields, fullDataMode, data);
|
|
203
125
|
|
|
204
126
|
return this as any as T;
|
|
205
|
-
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
206
130
|
|
|
207
131
|
private getModelScalarFields(model: RWSModel<T>): string[] {
|
|
208
132
|
return ModelUtils.getModelScalarFields(model);
|
|
@@ -449,7 +373,8 @@ class RWSModel<T> implements IModel {
|
|
|
449
373
|
const collection = Reflect.get(this, '_collection');
|
|
450
374
|
this.checkForInclusionWithThrow(this.name);
|
|
451
375
|
try {
|
|
452
|
-
const
|
|
376
|
+
const paginateParams = findParams?.pagination ? findParams?.pagination : undefined;
|
|
377
|
+
const dbData = await this.services.dbService.findBy(collection, conditions, fields, ordering, paginateParams);
|
|
453
378
|
|
|
454
379
|
if (dbData.length) {
|
|
455
380
|
const instanced: T[] = [];
|
|
@@ -5,13 +5,16 @@ export interface IDbOpts {
|
|
|
5
5
|
useText?: boolean;
|
|
6
6
|
maxLength?: number;
|
|
7
7
|
useUuid?: boolean;
|
|
8
|
+
params?: string[]
|
|
8
9
|
};
|
|
9
10
|
postgres?: {
|
|
10
11
|
useText?: boolean;
|
|
11
12
|
useUuid?: boolean;
|
|
13
|
+
params?: string[]
|
|
12
14
|
};
|
|
13
15
|
mongodb?: {
|
|
14
16
|
customType?: string;
|
|
17
|
+
params?: string[]
|
|
15
18
|
};
|
|
16
19
|
}
|
|
17
20
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { RWSModel } from "../core/RWSModel";
|
|
2
|
+
import { RelManyMetaType, RelOneMetaType } from "../types/RelationTypes";
|
|
3
|
+
import { IRWSModel } from "../../types/IRWSModel";
|
|
4
|
+
import { TimeSeriesUtils } from "./TimeSeriesUtils";
|
|
5
|
+
import { RelationUtils } from "./RelationUtils";
|
|
6
|
+
import { OpModelType } from "..";
|
|
7
|
+
import { ModelUtils } from "./ModelUtils";
|
|
8
|
+
|
|
9
|
+
export class HydrateUtils {
|
|
10
|
+
static async hydrateDataFields(model: RWSModel<any>, collections_to_models: {[key: string]: any}, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {[key: string] : any}){
|
|
11
|
+
const timeSeriesIds = TimeSeriesUtils.getTimeSeriesModelFields(model);
|
|
12
|
+
for (const key in data) {
|
|
13
|
+
if (data.hasOwnProperty(key)) {
|
|
14
|
+
if(!fullDataMode && ((model).constructor as OpModelType<any>)._CUT_KEYS.includes(key)){
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Object.keys(relOneData).includes(key)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (seriesHydrationfields.includes(key)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const timeSeriesMetaData = timeSeriesIds[key];
|
|
28
|
+
|
|
29
|
+
if (timeSeriesMetaData) {
|
|
30
|
+
model[key] = data[key];
|
|
31
|
+
const seriesModel = collections_to_models[timeSeriesMetaData.collection];
|
|
32
|
+
|
|
33
|
+
const dataModels = await seriesModel.findBy({
|
|
34
|
+
id: { in: data[key] }
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
seriesHydrationfields.push(timeSeriesMetaData.hydrationField);
|
|
38
|
+
|
|
39
|
+
model[timeSeriesMetaData.hydrationField] = dataModels;
|
|
40
|
+
} else {
|
|
41
|
+
model[key] = data[key];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static async hydrateRelations(model: RWSModel<any>, relManyData: RelManyMetaType<IRWSModel>, relOneData: RelOneMetaType<IRWSModel>, seriesHydrationfields: string[], fullDataMode: boolean, data: {[key: string] : any})
|
|
48
|
+
{
|
|
49
|
+
// Handle many-to-many relations
|
|
50
|
+
for (const key in relManyData) {
|
|
51
|
+
if(!fullDataMode && (model as any).constructor._CUT_KEYS.includes(key)){
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const relMeta = relManyData[key];
|
|
56
|
+
|
|
57
|
+
const relationEnabled = !RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
58
|
+
if (relationEnabled) {
|
|
59
|
+
model[relMeta.key] = await relMeta.inversionModel.findBy({
|
|
60
|
+
conditions: {
|
|
61
|
+
[relMeta.foreignKey]: data.id
|
|
62
|
+
},
|
|
63
|
+
allowRelations: false
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Handle one-to-one relations
|
|
69
|
+
for (const key in relOneData) {
|
|
70
|
+
if(!fullDataMode && ((model as any).constructor as OpModelType<any>)._CUT_KEYS.includes(key)){
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const relMeta = relOneData[key];
|
|
75
|
+
const relationEnabled = !RelationUtils.checkRelDisabled(model, relMeta.key);
|
|
76
|
+
|
|
77
|
+
if(!data[relMeta.hydrationField] && relMeta.required){
|
|
78
|
+
throw new Error(`Relation field "${relMeta.hydrationField}" is required in model ${this.constructor.name}.`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (relationEnabled && data[relMeta.hydrationField]) {
|
|
82
|
+
model[relMeta.key] = await relMeta.model.findOneBy({conditions: {[relMeta.foreignKey] : data[relMeta.hydrationField]}}, { allowRelations: false });
|
|
83
|
+
}
|
|
84
|
+
else if(relationEnabled && !data[relMeta.hydrationField] && data[relMeta.key]){
|
|
85
|
+
const newRelModel: RWSModel<any> = await relMeta.model.create(data[relMeta.key]);
|
|
86
|
+
model[relMeta.key] = await newRelModel.save();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const cutKeys = ((model.constructor as OpModelType<any>)._CUT_KEYS as string[]);
|
|
90
|
+
|
|
91
|
+
const trackedField = Object.keys((await ModelUtils.getModelAnnotations(model.constructor as OpModelType<any>))).includes(relMeta.hydrationField);
|
|
92
|
+
|
|
93
|
+
if(!cutKeys.includes(relMeta.hydrationField) && !trackedField){
|
|
94
|
+
cutKeys.push(relMeta.hydrationField)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// seriesHydrationfields.push(relMeta.hydrationField);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|