@hedhog/pagination 0.0.17 → 0.0.22

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.
Files changed (54) hide show
  1. package/dist/databases/abstract.database.d.ts +61 -0
  2. package/dist/databases/abstract.database.d.ts.map +1 -0
  3. package/dist/databases/abstract.database.js +643 -0
  4. package/dist/databases/abstract.database.js.map +1 -0
  5. package/dist/databases/database.d.ts +5 -0
  6. package/dist/databases/database.d.ts.map +1 -0
  7. package/dist/databases/database.factory.d.ts +7 -0
  8. package/dist/databases/database.factory.d.ts.map +1 -0
  9. package/dist/databases/database.factory.js +44 -0
  10. package/dist/databases/database.factory.js.map +1 -0
  11. package/dist/databases/database.js +9 -0
  12. package/dist/databases/database.js.map +1 -0
  13. package/dist/databases/index.d.ts +4 -0
  14. package/dist/databases/index.d.ts.map +1 -0
  15. package/dist/databases/index.js +20 -0
  16. package/dist/databases/index.js.map +1 -0
  17. package/dist/databases/mysql.database.d.ts +10 -0
  18. package/dist/databases/mysql.database.d.ts.map +1 -0
  19. package/dist/databases/mysql.database.js +17 -0
  20. package/dist/databases/mysql.database.js.map +1 -0
  21. package/dist/databases/postgres.database.d.ts +10 -0
  22. package/dist/databases/postgres.database.d.ts.map +1 -0
  23. package/dist/databases/postgres.database.js +17 -0
  24. package/dist/databases/postgres.database.js.map +1 -0
  25. package/dist/pagination.service.d.ts +18 -4
  26. package/dist/pagination.service.d.ts.map +1 -1
  27. package/dist/pagination.service.js +131 -5
  28. package/dist/pagination.service.js.map +1 -1
  29. package/dist/types/pagination.types.d.ts +1 -0
  30. package/dist/types/pagination.types.d.ts.map +1 -1
  31. package/dist/types/query-option.d.ts +5 -0
  32. package/dist/types/query-option.d.ts.map +1 -0
  33. package/dist/types/query-option.js +3 -0
  34. package/dist/types/query-option.js.map +1 -0
  35. package/dist/types/relation-n2n-result.d.ts +7 -0
  36. package/dist/types/relation-n2n-result.d.ts.map +1 -0
  37. package/dist/types/relation-n2n-result.js +3 -0
  38. package/dist/types/relation-n2n-result.js.map +1 -0
  39. package/dist/types/transaction-queries.d.ts +7 -0
  40. package/dist/types/transaction-queries.d.ts.map +1 -0
  41. package/dist/types/transaction-queries.js +3 -0
  42. package/dist/types/transaction-queries.js.map +1 -0
  43. package/package.json +6 -2
  44. package/src/databases/abstract.database.ts +833 -0
  45. package/src/databases/database.factory.ts +26 -0
  46. package/src/databases/database.ts +4 -0
  47. package/src/databases/index.ts +3 -0
  48. package/src/databases/mysql.database.ts +14 -0
  49. package/src/databases/postgres.database.ts +14 -0
  50. package/src/pagination.service.ts +229 -29
  51. package/src/types/pagination.types.ts +1 -0
  52. package/src/types/query-option.ts +4 -0
  53. package/src/types/relation-n2n-result.ts +6 -0
  54. package/src/types/transaction-queries.ts +7 -0
@@ -0,0 +1,26 @@
1
+ import * as chalk from 'chalk';
2
+ import { Database } from './database';
3
+ import { PostgresDatabase } from './postgres.database';
4
+ import { MySQLDatabase } from './mysql.database';
5
+
6
+ export class DatabaseFactory {
7
+ public static create(
8
+ type: Database,
9
+ host: string,
10
+ user: string,
11
+ password: string,
12
+ database: string,
13
+ port: number,
14
+ ) {
15
+ switch (type) {
16
+ case Database.POSTGRES:
17
+ return new PostgresDatabase(host, user, password, database, port);
18
+
19
+ case Database.MYSQL:
20
+ return new MySQLDatabase(host, user, password, database, port);
21
+
22
+ default:
23
+ console.info(chalk.yellow(`[WARN] Unsupported Database: ${type}`));
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,4 @@
1
+ export enum Database {
2
+ POSTGRES = 'postgres',
3
+ MYSQL = 'mysql',
4
+ }
@@ -0,0 +1,3 @@
1
+ export * from './database';
2
+ export * from './database.factory';
3
+ export * from './abstract.database';
@@ -0,0 +1,14 @@
1
+ import { AbstractDatabase } from './abstract.database';
2
+ import { Database } from './database';
3
+
4
+ export class MySQLDatabase extends AbstractDatabase {
5
+ constructor(
6
+ protected host: string,
7
+ protected user: string,
8
+ protected password: string,
9
+ protected database: string,
10
+ protected port: number,
11
+ ) {
12
+ super(Database.MYSQL, host, user, password, database, port);
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ import { AbstractDatabase } from './abstract.database';
2
+ import { Database } from './database';
3
+
4
+ export class PostgresDatabase extends AbstractDatabase {
5
+ constructor(
6
+ protected host: string,
7
+ protected user: string,
8
+ protected password: string,
9
+ protected database: string,
10
+ protected port: number,
11
+ ) {
12
+ super(Database.POSTGRES, host, user, password, database, port);
13
+ }
14
+ }
@@ -1,27 +1,23 @@
1
- import { itemTranslations } from '@hedhog/utils';
1
+ import { itemTranslations } from '@hedhog/core';
2
2
  import { BadRequestException, Injectable, Logger } from '@nestjs/common';
3
3
  import {
4
4
  DEFAULT_PAGE,
5
5
  DEFAULT_PAGE_SIZE,
6
6
  } from './constants/pagination.constants';
7
+ import { AbstractDatabase, Database, DatabaseFactory } from './databases';
7
8
  import { PageOrderDirection } from './enums/patination.enums';
8
- import type {
9
- BaseModel,
10
- FindManyArgs,
11
- PaginatedResult,
12
- PaginationParams,
13
- } from './types/pagination.types';
9
+ import type { FindManyArgs, PaginationParams } from './types/pagination.types';
14
10
 
15
11
  @Injectable()
16
12
  export class PaginationService {
17
13
  private readonly logger = new Logger(PaginationService.name);
18
-
19
- async paginate<T, M extends BaseModel>(
14
+ private db: any = null;
15
+ async paginate<T, M extends any>(
20
16
  model: M,
21
17
  paginationParams: PaginationParams,
22
18
  customQuery?: FindManyArgs<M>,
23
19
  translationKey?: string,
24
- ): Promise<PaginatedResult<T>> {
20
+ ) /*: Promise<PaginatedResult<T>>*/ {
25
21
  try {
26
22
  if (!model) {
27
23
  throw new BadRequestException('Model is required');
@@ -49,16 +45,25 @@ export class PaginationService {
49
45
 
50
46
  if (sortField) {
51
47
  const invalid = this.isInvalidField(sortField, model);
48
+ let localeInvalid = false;
52
49
  if (invalid) {
53
- this.logger.error(`Invalid field: ${sortField}`);
54
- throw new BadRequestException(
55
- `Invalid field: ${sortField}. Valid columns are: ${this.extractFieldNames(
56
- model,
57
- ).join(', ')}`,
58
- );
59
- }
50
+ localeInvalid = this.isInvalidLocaleField(sortField, model);
60
51
 
61
- sortOrderCondition = { [sortField]: sortOrder };
52
+ if (localeInvalid) {
53
+ this.logger.error(`Invalid field: ${sortField}`);
54
+ throw new BadRequestException(
55
+ `Invalid field: ${sortField}. Valid columns are: ${this.extractFieldNames(
56
+ model,
57
+ ).join(', ')}`,
58
+ );
59
+ } else {
60
+ sortOrderCondition = {
61
+ [`${(model as any).name}_locale`]: { [sortField]: sortOrder },
62
+ };
63
+ }
64
+ } else {
65
+ sortOrderCondition = { [sortField]: sortOrder };
66
+ }
62
67
  }
63
68
 
64
69
  if (search) {
@@ -94,29 +99,30 @@ export class PaginationService {
94
99
  const skip = page > 0 ? pageSize * (page - 1) : 0;
95
100
 
96
101
  if (
97
- customQuery.where &&
98
- customQuery.where.OR &&
99
- customQuery.where.OR.length === 0
102
+ (customQuery as any).where &&
103
+ (customQuery as any).where.OR &&
104
+ (customQuery as any).where.OR.length === 0
100
105
  ) {
101
- delete customQuery.where.OR;
106
+ delete (customQuery as any).where.OR;
102
107
  }
103
108
 
104
109
  const query: any = {
105
110
  select: selectCondition,
106
- where: customQuery?.where || {},
111
+ where: (customQuery as any)?.where || {},
107
112
  orderBy: sortOrderCondition,
108
113
  take: pageSize,
109
114
  skip,
110
115
  };
111
116
 
112
- if (customQuery?.include) {
113
- query.include = customQuery?.include;
117
+ if ((customQuery as any)?.include) {
118
+ query.include = (customQuery as any)?.include;
114
119
  delete query.select;
115
120
  }
116
121
 
117
122
  let [total, data] = await Promise.all([
118
- model.count({ where: customQuery?.where || {} }),
119
- model.findMany(query),
123
+ (model as any).count({ where: (customQuery as any)?.where || {} }),
124
+ (model as any).findMany(query),
125
+ //this.query(model, query),
120
126
  ]);
121
127
 
122
128
  const lastPage = Math.ceil(total / pageSize);
@@ -159,13 +165,207 @@ export class PaginationService {
159
165
  return fieldNames;
160
166
  }
161
167
 
162
- isInvalidField(sortField: string, model: BaseModel): boolean {
168
+ isInvalidField(sortField: string, model: any): boolean {
163
169
  return model && model.fields ? !model.fields[sortField] : true;
164
170
  }
165
171
 
166
- isInvalidFields(fields: string[], model: BaseModel): boolean {
172
+ isInvalidLocaleField(sortField: string, model: any): boolean {
173
+ const fields = model['$parent'][`${model.name}_locale`].fields;
174
+
175
+ return model && fields ? !fields[sortField] : true;
176
+ }
177
+
178
+ isInvalidFields(fields: string[], model: any): boolean {
167
179
  return !fields.every((field) =>
168
180
  model.fields ? model && model.fields[field] : false,
169
181
  );
170
182
  }
183
+
184
+ isInvalidLocaleFields(fields: string[], model: any): boolean {
185
+ const localeFields = model['$parent'][`${model.name}_locale`].fields;
186
+
187
+ return !fields.every((field) =>
188
+ localeFields ? !localeFields[field] : false,
189
+ );
190
+ }
191
+
192
+ async getDb(model: any): Promise<any> {
193
+ const {
194
+ DATABASE_URL,
195
+ DB_HOST,
196
+ DB_PORT,
197
+ DB_USERNAME,
198
+ DB_PASSWORD,
199
+ DB_DATABASE,
200
+ } = model['$parent']._engine.config.env;
201
+
202
+ const type = DATABASE_URL.split(':')[0];
203
+
204
+ this.db = DatabaseFactory.create(
205
+ type === 'mysql' ? Database.MYSQL : Database.POSTGRES,
206
+ DB_HOST,
207
+ DB_USERNAME,
208
+ DB_PASSWORD,
209
+ DB_DATABASE,
210
+ Number(DB_PORT),
211
+ );
212
+
213
+ return this.db;
214
+ }
215
+
216
+ async getBuilder(
217
+ model: any,
218
+ tableName: string,
219
+ query: any,
220
+ builder: any,
221
+ ): Promise<any> {
222
+ const db = await this.getDb(model);
223
+
224
+ if (!builder) {
225
+ builder = {
226
+ joinTables: [],
227
+ select: [],
228
+ where: [],
229
+ order: [],
230
+ join: [],
231
+ from: db.getColumnNameWithScaping(tableName),
232
+ };
233
+ }
234
+
235
+ if (query.orderBy) {
236
+ for (const key in query.orderBy) {
237
+ if (typeof query.orderBy[key] === 'object') {
238
+ if (!builder.joinTables.includes(key)) {
239
+ builder.joinTables.push(key);
240
+ const foreignKey = await db.getColumnNameFromRelation(
241
+ tableName,
242
+ `${tableName}_locale`,
243
+ );
244
+
245
+ const primaryKeys = await db.getPrimaryKeys(tableName);
246
+
247
+ if (primaryKeys.length !== 1) {
248
+ throw new Error('Only single primary key is supported');
249
+ }
250
+
251
+ const primaryKey = primaryKeys[0];
252
+
253
+ builder.join.push(
254
+ `LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(foreignKey)} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(primaryKey)}`,
255
+ );
256
+
257
+ for (const k in query.orderBy[key]) {
258
+ builder.order.push(
259
+ `${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(k)} ${query.orderBy[key][k]}`,
260
+ );
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ if (query.select) {
268
+ builder.select = [
269
+ ...builder.select,
270
+ ...Object.keys(query.select).map(
271
+ (key) =>
272
+ `${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(key)}`,
273
+ ),
274
+ ];
275
+ for (const key in query.select) {
276
+ if (typeof query.select[key] === 'object') {
277
+ builder = await this.getBuilder(
278
+ model,
279
+ key,
280
+ query.select[key],
281
+ builder,
282
+ );
283
+ }
284
+ }
285
+ } else if (query.include) {
286
+ builder.select = [
287
+ ...builder.select,
288
+ `${db.getColumnNameWithScaping(tableName)}.*`,
289
+ ];
290
+ for (const key in query.include) {
291
+ if (typeof query.include[key] === 'object') {
292
+ if (!builder.joinTables.includes(key)) {
293
+ builder.joinTables.push(key);
294
+
295
+ const foreignKey = await db.getColumnNameFromRelation(
296
+ tableName,
297
+ key,
298
+ );
299
+
300
+ const primaryKeys = await db.getPrimaryKeys(tableName);
301
+
302
+ const primaryKey = primaryKeys[0];
303
+
304
+ builder.join.push(
305
+ `LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(foreignKey)} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(primaryKey)}`,
306
+ );
307
+
308
+ builder = await this.getBuilder(
309
+ model,
310
+ key,
311
+ query.include[key],
312
+ builder,
313
+ );
314
+ }
315
+ }
316
+ }
317
+ }
318
+
319
+ if (query.where) {
320
+ for (const key in query.where) {
321
+ if (typeof query.where[key] === 'object') {
322
+ if (!builder.joinTables.includes(key)) {
323
+ builder.joinTables.push(key);
324
+ const foreignKey = await db.getColumnNameFromRelation(
325
+ key,
326
+ tableName,
327
+ );
328
+ const primaryKeys = await db.getPrimaryKeys(key);
329
+
330
+ builder.join.push(
331
+ `LEFT JOIN ${db.getColumnNameWithScaping(key)} ON ${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(primaryKeys[0])} = ${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(foreignKey)}`,
332
+ );
333
+
334
+ for (const k in query.where[key]) {
335
+ builder.where.push(
336
+ `${db.getColumnNameWithScaping(key)}.${db.getColumnNameWithScaping(k)} = ${AbstractDatabase.addSimpleQuotes(query.where[key][k])}`,
337
+ );
338
+ }
339
+ }
340
+ } else {
341
+ builder.where.push(
342
+ `${db.getColumnNameWithScaping(tableName)}.${db.getColumnNameWithScaping(key)} = ${AbstractDatabase.addSimpleQuotes(query.where[key])}`,
343
+ );
344
+ }
345
+ }
346
+ }
347
+
348
+ return builder;
349
+ }
350
+
351
+ async query(model: any, query: any): Promise<any[]> {
352
+ const db = await this.getDb(model);
353
+ const builder = await this.getBuilder(model, model.name, query, null);
354
+
355
+ const sql = [];
356
+
357
+ sql.push(`SELECT ${builder.select.join(', ')}`);
358
+ sql.push(`FROM ${builder.from}`);
359
+ if (builder.join.length) sql.push(builder.join.join(' '));
360
+ if (builder.where.length) sql.push(`WHERE ${builder.where.join(' AND ')}`);
361
+ if (builder.order.length) sql.push(`ORDER BY ${builder.order.join(', ')}`);
362
+ if (query.take >= 0 && query.skip >= 0)
363
+ sql.push(db.getLimit(query.take, query.skip));
364
+
365
+ console.log('sql', sql.join(' '));
366
+
367
+ const result = await db.query(sql.join(' '));
368
+
369
+ return result;
370
+ }
171
371
  }
@@ -36,6 +36,7 @@ export type BaseModel = {
36
36
  findMany: (args: any) => Promise<any[]>;
37
37
  count: (args: any) => Promise<number>;
38
38
  fields?: Record<string, any>;
39
+ name: string;
39
40
  };
40
41
 
41
42
  export type FindManyArgs<M> = M extends { findMany: (args: infer A) => any }
@@ -0,0 +1,4 @@
1
+ export type QueryOption = {
2
+ returning?: string[] | string;
3
+ primaryKeys?: string[] | string;
4
+ };
@@ -0,0 +1,6 @@
1
+ export type RelationN2NResult = {
2
+ tableNameIntermediate: string;
3
+ columnNameOrigin: string;
4
+ columnNameDestination: string;
5
+ primaryKeyDestination: string;
6
+ };
@@ -0,0 +1,7 @@
1
+ import { QueryOption } from './query-option';
2
+
3
+ export type TransactionQueries = {
4
+ query: string;
5
+ values?: any[];
6
+ options?: QueryOption;
7
+ };