@hedhog/pagination 0.0.16 → 0.0.18

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