@reldens/storage 0.59.0 → 0.61.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.
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  const { EntityManager } = require('./entity-manager');
8
- const { ErrorManager, sc } = require('@reldens/utils');
8
+ const { ErrorManager, Logger, sc } = require('@reldens/utils');
9
9
 
10
10
  class BaseDataServer
11
11
  {
@@ -16,6 +16,9 @@ class BaseDataServer
16
16
  if(!this.config.host){
17
17
  this.config.host = 'localhost';
18
18
  }
19
+ if(!this.config.port){
20
+ this.config.port = 3306;
21
+ }
19
22
  this.client = sc.get(props, 'client', '');
20
23
  this.poolConfig = sc.get(props, 'poolConfig', {});
21
24
  this.connectStringOptions = sc.get(props, 'connectStringOptions', '');
@@ -27,16 +30,23 @@ class BaseDataServer
27
30
  this.initialized = sc.get(props, 'initialized', false);
28
31
  this.rawEntities = props.rawEntities;
29
32
  this.entities = props.entities || {};
33
+ this.entitiesConfig = sc.get(props, 'entitiesConfig', {});
30
34
  this.entityManager = new EntityManager({entities: this.entities});
31
35
  }
32
36
 
33
37
  createConnectionString()
34
38
  {
35
- return (this.client || 'client-not-specified')+'://'
36
- +(this.config.user || 'user-not-specified')
39
+ if('' === this.client){
40
+ Logger.warning('Client is empty, connection may fail.');
41
+ }
42
+ if('' === this.config.user){
43
+ Logger.warning('User is empty, connection may fail.');
44
+ }
45
+ return (this.client || '')+'://'
46
+ +(this.config.user || '')
37
47
  +(this.config.password ? ':'+this.config.password : '')
38
48
  +'@'+this.config.host
39
- +':'+(this.config.port || 3306)
49
+ +':'+this.config.port
40
50
  +(this.config.database ? '/'+this.config.database : '')
41
51
  +(this.connectStringOptions ? '?'+this.connectStringOptions : '');
42
52
  }
@@ -20,6 +20,7 @@ class BaseDriver
20
20
  this.offset = sc.get(props, 'offset', 0);
21
21
  this.sortBy = sc.get(props, 'sortBy', false);
22
22
  this.sortDirection = sc.get(props, 'sortDirection', 'ASC');
23
+ this.entityConfig = sc.get(props, 'entityConfig', {});
23
24
  }
24
25
 
25
26
  databaseName()
@@ -164,7 +165,7 @@ class BaseDriver
164
165
 
165
166
  executeCustomQuery(methodName, methodOptions)
166
167
  {
167
- if(typeof this.rawModel[methodName] !== 'function'){
168
+ if('function' !== typeof this.rawModel[methodName]){
168
169
  Logger.error('Custom query method not found in raw model.', methodName, this.rawModel);
169
170
  return false;
170
171
  }
@@ -176,6 +177,68 @@ class BaseDriver
176
177
  ErrorManager.error('BaseDriver rawQuery() not implemented.');
177
178
  }
178
179
 
180
+ isJsonField(fieldName)
181
+ {
182
+ if(!this.entityConfig || !this.rawName){
183
+ return false;
184
+ }
185
+ let properties = sc.get(this.entityConfig, 'properties', {});
186
+ let fieldConfig = sc.get(properties, fieldName, {});
187
+ let fieldType = sc.get(fieldConfig, 'type', '');
188
+ return 'json' === fieldType.toLowerCase();
189
+ }
190
+
191
+ parseRelationsString(relationsString)
192
+ {
193
+ if(!relationsString || '' === relationsString.trim()){
194
+ return [];
195
+ }
196
+ return relationsString.split(',').map(relation => relation.trim()).filter(relation => '' !== relation);
197
+ }
198
+
199
+ applyQueryOptions(queryOptions)
200
+ {
201
+ if(sc.hasOwn(queryOptions, 'limit')){
202
+ this.limit = queryOptions.limit;
203
+ }
204
+ if(sc.hasOwn(queryOptions, 'offset')){
205
+ this.offset = queryOptions.offset;
206
+ }
207
+ if(sc.hasOwn(queryOptions, 'sortBy')){
208
+ this.sortBy = queryOptions.sortBy;
209
+ }
210
+ if(sc.hasOwn(queryOptions, 'sortDirection')){
211
+ this.sortDirection = queryOptions.sortDirection;
212
+ }
213
+ }
214
+
215
+ preserveEntityState()
216
+ {
217
+ return {
218
+ limit: this.limit,
219
+ offset: this.offset,
220
+ sortBy: this.sortBy,
221
+ sortDirection: this.sortDirection
222
+ };
223
+ }
224
+
225
+ restoreEntityState(originalState)
226
+ {
227
+ this.limit = originalState.limit;
228
+ this.offset = originalState.offset;
229
+ this.sortBy = originalState.sortBy;
230
+ this.sortDirection = originalState.sortDirection;
231
+ }
232
+
233
+ async loadEntityData(filters, queryOptions, relationsString)
234
+ {
235
+ this.applyQueryOptions(queryOptions);
236
+ let relations = this.parseRelationsString(relationsString);
237
+ return 0 < relations.length
238
+ ? await this.loadWithRelations(filters, relations)
239
+ : 0 < Object.keys(filters).length ? await this.load(filters) : await this.loadAll();
240
+ }
241
+
179
242
  }
180
243
 
181
244
  module.exports.BaseDriver = BaseDriver;
@@ -68,7 +68,8 @@ class MikroOrmDriver extends BaseDriver
68
68
 
69
69
  async update(filters, updatePatch)
70
70
  {
71
- let entities = await this.repository.find(filters, this.queryBuilder(true, true, true));
71
+ let processedFilters = this.processFilters(filters);
72
+ let entities = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
72
73
  if(0 === entities.length){
73
74
  return false;
74
75
  }
@@ -83,7 +84,8 @@ class MikroOrmDriver extends BaseDriver
83
84
  async updateBy(field, fieldValue, updatePatch, operator = null)
84
85
  {
85
86
  let filter = this.createSingleFilter(field, fieldValue, operator);
86
- let entities = await this.repository.find(filter, this.queryBuilder(true, true, true));
87
+ let processedFilters = this.processFilters(filter);
88
+ let entities = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
87
89
  if(0 === entities.length){
88
90
  return false;
89
91
  }
@@ -118,8 +120,9 @@ class MikroOrmDriver extends BaseDriver
118
120
 
119
121
  async delete(filters = {})
120
122
  {
121
- let entries = await this.load(filters);
122
- if (!entries){
123
+ let processedFilters = this.processFilters(filters);
124
+ let entries = await this.repository.find(processedFilters);
125
+ if(!entries){
123
126
  return false;
124
127
  }
125
128
  for(let entry of entries){
@@ -134,13 +137,14 @@ class MikroOrmDriver extends BaseDriver
134
137
 
135
138
  async count(filters)
136
139
  {
137
- return this.repository.count(filters);
140
+ let processedFilters = this.processFilters(filters);
141
+ return this.repository.count(processedFilters);
138
142
  }
139
143
 
140
144
  async countWithRelations(filters, relations)
141
145
  {
142
- // @TODO - BETA - Look a way to filter by sub-entities properties before count.
143
- return this.count(filters);
146
+ let processedFilters = this.processFilters(filters);
147
+ return this.repository.count(processedFilters);
144
148
  }
145
149
 
146
150
  loadAll()
@@ -156,74 +160,135 @@ class MikroOrmDriver extends BaseDriver
156
160
 
157
161
  load(filters)
158
162
  {
159
- return this.repository.find(filters, this.queryBuilder(true, true, true));
163
+ let processedFilters = this.processFilters(filters);
164
+ return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
160
165
  }
161
166
 
162
167
  async loadWithRelations(filters, relations)
163
168
  {
164
- let entitiesCollection = await this.repository.findAll(filters, this.queryBuilder(true, true, true));
169
+ let processedFilters = this.processFilters(filters);
170
+ let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
165
171
  return await this.appendRelationsToCollection(entitiesCollection, relations);
166
172
  }
167
173
 
168
174
  loadBy(field, fieldValue, operator = null)
169
175
  {
170
176
  let filter = this.createSingleFilter(field, fieldValue, operator);
171
- return this.repository.find(filter, this.queryBuilder(true, true, true));
177
+ let processedFilters = this.processFilters(filter);
178
+ return this.repository.find(processedFilters, this.queryBuilder(true, true, true));
172
179
  }
173
180
 
174
181
  async loadByWithRelations(field, fieldValue, relations, operator = null)
175
182
  {
176
183
  let filter = this.createSingleFilter(field, fieldValue, operator);
177
- let entitiesCollection = await this.repository.find(filter, this.queryBuilder(true, true, true));
184
+ let processedFilters = this.processFilters(filter);
185
+ let entitiesCollection = await this.repository.find(processedFilters, this.queryBuilder(true, true, true));
178
186
  return await this.appendRelationsToCollection(entitiesCollection, relations);
179
187
  }
180
188
 
181
189
  loadById(id)
182
190
  {
183
- return this.loadBy({id});
191
+ return this.loadBy('id', id);
184
192
  }
185
193
 
186
194
  async loadByIdWithRelations(id, relations)
187
195
  {
188
- let entity = await this.loadBy({id});
196
+ let entity = await this.loadBy('id', id);
189
197
  return await this.appendRelationsToCollection(entity, relations);
190
198
  }
191
199
 
192
200
  loadByIds(ids)
193
201
  {
194
- this.repository.find({id: {$in: ids}});
202
+ let processedFilters = this.processFilters({id: {$in: ids}});
203
+ return this.repository.find(processedFilters);
195
204
  }
196
205
 
197
206
  loadOne(filters)
198
207
  {
199
- return this.repository.findOne(filters, this.queryBuilder(false, true, true));
208
+ let processedFilters = this.processFilters(filters);
209
+ return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
200
210
  }
201
211
 
202
212
  async loadOneWithRelations(filters, relations)
203
213
  {
204
- let entitiesCollection = await this.loadOne(filters);
214
+ let processedFilters = this.processFilters(filters);
215
+ let entitiesCollection = await this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
205
216
  return this.appendRelationsToCollection(entitiesCollection, relations);
206
217
  }
207
218
 
208
219
  loadOneBy(field, fieldValue, operator = null)
209
220
  {
210
221
  let filter = this.createSingleFilter(field, fieldValue, operator);
211
- return this.repository.findOne(filter, this.queryBuilder(false, true, true));
222
+ let processedFilters = this.processFilters(filter);
223
+ return this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
212
224
  }
213
225
 
214
226
  async loadOneByWithRelations(field, fieldValue, relations, operator = null)
215
227
  {
216
228
  let filter = this.createSingleFilter(field, fieldValue, operator);
217
- let entitiesCollection = await this.repository.findOne(filter, this.queryBuilder(false, true, true));
229
+ let processedFilters = this.processFilters(filter);
230
+ let entitiesCollection = await this.repository.findOne(processedFilters, this.queryBuilder(false, true, true));
218
231
  return this.appendRelationsToCollection(entitiesCollection, relations);
219
232
  }
220
233
 
221
- // @TODO - BETA - Refactor and improve.
234
+ processFilters(filters)
235
+ {
236
+ if(!sc.isObject(filters)){
237
+ return filters;
238
+ }
239
+ let processedFilters = {};
240
+ for(let key of Object.keys(filters)){
241
+ let value = filters[key];
242
+ if('OR' === key && sc.isArray(value)){
243
+ processedFilters.$or = value.map(condition => this.processFilters(condition));
244
+ continue;
245
+ }
246
+ if(sc.hasOwn(value, 'operator') && sc.hasOwn(value, 'value')){
247
+ processedFilters[key] = this.applyOperator(value.value, value.operator, key);
248
+ continue;
249
+ }
250
+ if(sc.isObject(value) && !sc.isArray(value)){
251
+ processedFilters[key] = this.processFilters(value);
252
+ continue;
253
+ }
254
+ processedFilters[key] = value;
255
+ }
256
+ return processedFilters;
257
+ }
258
+
259
+ applyOperator(value, operator, fieldName = null)
260
+ {
261
+ let upperOperator = operator ? operator.toUpperCase() : '';
262
+ if('LIKE' === upperOperator){
263
+ return this.handleLikeOperator(value, fieldName);
264
+ }
265
+ let operatorsMap = {
266
+ '=': value,
267
+ '!=': {$ne: value},
268
+ '>': {$gt: value},
269
+ '>=': {$gte: value},
270
+ '<': {$lt: value},
271
+ '<=': {$lte: value},
272
+ 'IN': {$in: value},
273
+ 'NOT IN': {$nin: value}
274
+ };
275
+ return operatorsMap[upperOperator] || value;
276
+ }
277
+
278
+ handleLikeOperator(value, fieldName)
279
+ {
280
+ let cleanValue = String(value).replace(/%/g, '');
281
+ if(this.isJsonField(fieldName)){
282
+ return {$like: '%' + cleanValue + '%'};
283
+ }
284
+ return {$like: '%' + cleanValue + '%'};
285
+ }
286
+
222
287
  queryBuilder(useLimit = false, useOffset = false, useSort = false)
223
288
  {
224
289
  let queryBuilder = {};
225
290
  if(0 < this.select.length){
226
- queryBuilder.select(...this.select);
291
+ queryBuilder.select = this.select;
227
292
  }
228
293
  if(useLimit && 0 !== this.limit){
229
294
  queryBuilder.limit = this.limit;
@@ -239,7 +304,6 @@ class MikroOrmDriver extends BaseDriver
239
304
  return queryBuilder;
240
305
  }
241
306
 
242
- // @TODO - BETA - Refactor and improve.
243
307
  createSingleFilter(field, fieldValue, operator = null)
244
308
  {
245
309
  let filter = {};
@@ -247,15 +311,12 @@ class MikroOrmDriver extends BaseDriver
247
311
  filter[field] = fieldValue;
248
312
  return filter;
249
313
  }
250
- filter[field] = {};
251
- filter[field][operator] = fieldValue;
314
+ filter[field] = {operator, value: fieldValue};
252
315
  return filter;
253
316
  }
254
317
 
255
- // @TODO - BETA - Refactor and improve.
256
318
  async appendRelationsToCollection(entitiesCollection, relations)
257
319
  {
258
- // @TODO - BETA - Refactor. I would like to use populate but it may not work if the driver is not Mongo DB.
259
320
  if('function' !== typeof this.rawModel.entity.relationMappings){
260
321
  return entitiesCollection;
261
322
  }
@@ -276,7 +337,6 @@ class MikroOrmDriver extends BaseDriver
276
337
  return entitiesCollection;
277
338
  }
278
339
 
279
- // @TODO - BETA - Refactor and improve.
280
340
  async appendRelatedEntities(entity, relations)
281
341
  {
282
342
  if('function' !== typeof this.rawModel.entity.relationMappings){
@@ -298,7 +358,6 @@ class MikroOrmDriver extends BaseDriver
298
358
  return entity;
299
359
  }
300
360
 
301
- // @TODO - BETA - Refactor and improve.
302
361
  async createNested(newInstance, params)
303
362
  {
304
363
  if('function' !== typeof this.rawModel.entity.relationMappings){
@@ -326,7 +385,6 @@ class MikroOrmDriver extends BaseDriver
326
385
  }
327
386
  }
328
387
 
329
- // @TODO - BETA - Refactor and improve.
330
388
  async createOne(params, i, relationEntity, newInstance, relation)
331
389
  {
332
390
  params[i] = newInstance[relation.join.from];
@@ -336,7 +394,6 @@ class MikroOrmDriver extends BaseDriver
336
394
  newInstance[i] = nestedObject;
337
395
  }
338
396
 
339
- // @TODO - BETA - Refactor and improve.
340
397
  async createMany(params, i, relationEntity, newInstance, relation)
341
398
  {
342
399
  let nestedArray = [];
@@ -208,10 +208,29 @@ class ObjectionJsDriver extends BaseDriver
208
208
 
209
209
  appendSingleFilter(queryBuilder, field, fieldValue, operator = null)
210
210
  {
211
- (null === operator) ? queryBuilder.where(field, fieldValue) : queryBuilder.where(field, operator, fieldValue);
211
+ if(null === operator){
212
+ queryBuilder.where(field, fieldValue);
213
+ return queryBuilder;
214
+ }
215
+ if('LIKE' === operator.toUpperCase()){
216
+ this.handleLikeFilter(queryBuilder, field, fieldValue);
217
+ return queryBuilder;
218
+ }
219
+ queryBuilder.where(field, operator, fieldValue);
212
220
  return queryBuilder;
213
221
  }
214
222
 
223
+ handleLikeFilter(queryBuilder, field, fieldValue)
224
+ {
225
+ let cleanValue = String(fieldValue).replace(/%/g, '');
226
+ if(this.isJsonField(field)){
227
+ let { ref } = this.rawModel.knex();
228
+ queryBuilder.where(ref(field).castText(), 'like', '%' + cleanValue + '%');
229
+ return;
230
+ }
231
+ queryBuilder.where(field, 'like', '%' + cleanValue + '%');
232
+ }
233
+
215
234
  appendFilters(queryBuilder, filters = {})
216
235
  {
217
236
  let filtersKeys = Object.keys(filters);
@@ -220,6 +239,16 @@ class ObjectionJsDriver extends BaseDriver
220
239
  }
221
240
  for(let i of filtersKeys){
222
241
  let filter = filters[i];
242
+ if('OR' === i && sc.isArray(filter)){
243
+ queryBuilder.where(function(){
244
+ for(let condition of filter){
245
+ this.orWhere(function(){
246
+ this.appendFilters(this, condition);
247
+ }.bind(this));
248
+ }
249
+ }.bind(this));
250
+ continue;
251
+ }
223
252
  let filterOperator = sc.get(filter, 'operator');
224
253
  if(filterOperator){
225
254
  if('OR' === filterOperator){
@@ -232,6 +261,11 @@ class ObjectionJsDriver extends BaseDriver
232
261
  }
233
262
  if('NOT' === filterOperator){
234
263
  queryBuilder.whereNot(i, filter.value);
264
+ continue;
265
+ }
266
+ if('LIKE' === filterOperator){
267
+ this.handleLikeFilter(queryBuilder, i, filter.value);
268
+ continue;
235
269
  }
236
270
  queryBuilder.where(i, filter.operator, filter.value);
237
271
  continue;
@@ -258,12 +292,12 @@ class ObjectionJsDriver extends BaseDriver
258
292
  if(!sc.isArray(relationsModifiers)){
259
293
  return queryBuilder;
260
294
  }
261
- for (let relationModifier of relationsModifiers) {
295
+ for(let relationModifier of relationsModifiers){
262
296
  let relationKey = sc.get(relationModifier, 'key');
263
- if (!relationKey) {
297
+ if(!relationKey){
264
298
  continue;
265
299
  }
266
- if (-1 === relations.indexOf(relationKey)) {
300
+ if(-1 === relations.indexOf(relationKey)){
267
301
  continue;
268
302
  }
269
303
  let relationOrderBy = sc.get(relationModifier, 'orderBy', {});