@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.
@@ -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 : `${source._collection}_id`,
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 _DEFAULTS = { required: false, many: false, embed: false, cascade: { onDelete: 'SetNull', onUpdate: 'Cascade' } };
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
@@ -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, false)}\n`;
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}", ${cascadeOpts.join(', ')})\n`;
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}", ${cascadeOpts.join(', ')})\n`;
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
- section += `\t@@${superTag.tagType}([${superTag.fields.join(', ')}]${mapStr})\n`;
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
- input = 'Int';
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
- else if (metadata.dbOptions.mysql.maxLength) {
80
- tags.push(`@db.VarChar(${metadata.dbOptions.mysql.maxLength})`);
100
+ if (tag) {
101
+ tags.push(tag);
81
102
  }
82
- if (metadata.dbOptions.mysql.useUuid && ((_a = metadata.tags) === null || _a === void 0 ? void 0 : _a.includes('id'))) {
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 && ((_b = metadata.tags) === null || _b === void 0 ? void 0 : _b.includes('id'))) {
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
- }>, debug?: boolean): string;
21
+ }>, optional?: boolean): string;
22
22
  }
23
23
  export declare const workspaceRootPath: string;
24
24
  export declare const moduleDirPath: string;
@@ -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, debug = false) {
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<import("..").RelOneMetaType<import("../..").IRWSModel>>;
35
+ static getRelationOneMeta(model: any, classFields: string[]): Promise<RelOneMetaType<IRWSModel>>;
34
36
  private getRelationManyMeta;
35
- static getRelationManyMeta(model: any, classFields: string[]): Promise<import("..").RelManyMetaType<import("../..").IRWSModel>>;
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
- // Handle many-to-many relations
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
- for (const key in data) {
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 dbData = await this.services.dbService.findBy(collection, conditions, fields, ordering);
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,7 +1,7 @@
1
1
  {
2
2
  "name": "@rws-framework/db",
3
3
  "private": false,
4
- "version": "3.0.1",
4
+ "version": "3.2.0",
5
5
  "description": "",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -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 : `${source._collection}_id`,
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 _DEFAULTS: Partial<IRelationOpts> = { required: false, many: false, embed: false, cascade: { onDelete: 'SetNull', onUpdate: 'Cascade' }};
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 = {
@@ -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, false)}\n`;
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}", ${cascadeOpts.join(', ')})\n`;
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}", ${cascadeOpts.join(', ')})\n`;
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
- section += `\t@@${superTag.tagType}([${superTag.fields.join(', ')}]${mapStr})\n`;
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
- input = 'Int';
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
- } else if (metadata.dbOptions.mysql.maxLength) {
89
- tags.push(`@db.VarChar(${metadata.dbOptions.mysql.maxLength})`);
113
+ }
114
+
115
+ if(tag){
116
+ tags.push(tag);
90
117
  }
91
118
 
92
119
  if (metadata.dbOptions.mysql.useUuid && metadata.tags?.includes('id')) {
@@ -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
- debug = false
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
- // Handle many-to-many relations
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
- for (const key in data) {
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 dbData = await this.services.dbService.findBy(collection, conditions, fields, ordering);
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
+ }