@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.
- package/dist/databases/abstract.database.d.ts +61 -0
- package/dist/databases/abstract.database.d.ts.map +1 -0
- package/dist/databases/abstract.database.js +643 -0
- package/dist/databases/abstract.database.js.map +1 -0
- package/dist/databases/database.d.ts +5 -0
- package/dist/databases/database.d.ts.map +1 -0
- package/dist/databases/database.factory.d.ts +7 -0
- package/dist/databases/database.factory.d.ts.map +1 -0
- package/dist/databases/database.factory.js +44 -0
- package/dist/databases/database.factory.js.map +1 -0
- package/dist/databases/database.js +9 -0
- package/dist/databases/database.js.map +1 -0
- package/dist/databases/index.d.ts +4 -0
- package/dist/databases/index.d.ts.map +1 -0
- package/dist/databases/index.js +20 -0
- package/dist/databases/index.js.map +1 -0
- package/dist/databases/mysql.database.d.ts +10 -0
- package/dist/databases/mysql.database.d.ts.map +1 -0
- package/dist/databases/mysql.database.js +17 -0
- package/dist/databases/mysql.database.js.map +1 -0
- package/dist/databases/postgres.database.d.ts +10 -0
- package/dist/databases/postgres.database.d.ts.map +1 -0
- package/dist/databases/postgres.database.js +17 -0
- package/dist/databases/postgres.database.js.map +1 -0
- package/dist/pagination.service.d.ts +18 -4
- package/dist/pagination.service.d.ts.map +1 -1
- package/dist/pagination.service.js +131 -5
- package/dist/pagination.service.js.map +1 -1
- package/dist/types/pagination.types.d.ts +1 -0
- package/dist/types/pagination.types.d.ts.map +1 -1
- package/dist/types/query-option.d.ts +5 -0
- package/dist/types/query-option.d.ts.map +1 -0
- package/dist/types/query-option.js +3 -0
- package/dist/types/query-option.js.map +1 -0
- package/dist/types/relation-n2n-result.d.ts +7 -0
- package/dist/types/relation-n2n-result.d.ts.map +1 -0
- package/dist/types/relation-n2n-result.js +3 -0
- package/dist/types/relation-n2n-result.js.map +1 -0
- package/dist/types/transaction-queries.d.ts +7 -0
- package/dist/types/transaction-queries.d.ts.map +1 -0
- package/dist/types/transaction-queries.js +3 -0
- package/dist/types/transaction-queries.js.map +1 -0
- package/package.json +6 -2
- package/src/databases/abstract.database.ts +833 -0
- package/src/databases/database.factory.ts +26 -0
- package/src/databases/database.ts +4 -0
- package/src/databases/index.ts +3 -0
- package/src/databases/mysql.database.ts +14 -0
- package/src/databases/postgres.database.ts +14 -0
- package/src/pagination.service.ts +229 -29
- package/src/types/pagination.types.ts +1 -0
- package/src/types/query-option.ts +4 -0
- package/src/types/relation-n2n-result.ts +6 -0
- 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,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/
|
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
|
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
|
-
)
|
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.
|
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
|
-
|
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:
|
168
|
+
isInvalidField(sortField: string, model: any): boolean {
|
163
169
|
return model && model.fields ? !model.fields[sortField] : true;
|
164
170
|
}
|
165
171
|
|
166
|
-
|
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
|
}
|