@hedhog/pagination 0.0.17 → 0.0.18

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 +16 -2
  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 +216 -12
  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,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
+ };