@hedhog/pagination 0.0.17 → 0.0.22

Sign up to get free protection for your applications and to get access to all the features.
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
+ };