@pcg/core 1.0.0-alpha.0
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/.turbo/turbo-build.log +15 -0
- package/CHANGELOG.md +7 -0
- package/dist/index.d.ts +1400 -0
- package/dist/index.js +2171 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
- package/src/abstracts/index.ts +3 -0
- package/src/abstracts/nestjs-resource-service.ts +154 -0
- package/src/abstracts/nestjs-service.ts +25 -0
- package/src/configs/app.config.ts +185 -0
- package/src/configs/db.config.ts +122 -0
- package/src/configs/index.ts +4 -0
- package/src/configs/logger.config.ts +62 -0
- package/src/context/action-context.ts +34 -0
- package/src/context/current-user.ts +49 -0
- package/src/context/index.ts +5 -0
- package/src/context/platform-method-context.ts +5 -0
- package/src/context/service-method-context.ts +47 -0
- package/src/db/snake-naming.strategy.ts +277 -0
- package/src/enums/app-env.enum.ts +36 -0
- package/src/enums/app-mode.enum.ts +5 -0
- package/src/enums/app-server.enum.ts +39 -0
- package/src/enums/index.ts +4 -0
- package/src/enums/worker-mode.enum.ts +11 -0
- package/src/errors/access-denied.error.ts +18 -0
- package/src/errors/bad-request.error.ts +9 -0
- package/src/errors/forbidden.error.ts +9 -0
- package/src/errors/index.ts +8 -0
- package/src/errors/input-validation.error.ts +16 -0
- package/src/errors/nest-error.filter.ts +70 -0
- package/src/errors/nest-error.ts +63 -0
- package/src/errors/not-found.error.ts +9 -0
- package/src/errors/unauthorized.error.ts +9 -0
- package/src/exceptions/http-exception-response.ts +34 -0
- package/src/exceptions/http-exceptions.filter.ts +95 -0
- package/src/index.ts +32 -0
- package/src/jwt/extractors.ts +80 -0
- package/src/jwt/types.ts +209 -0
- package/src/logger/classes/logger-factory.ts +54 -0
- package/src/logger/classes/logger.ts +340 -0
- package/src/logger/classes/nest-system-logger.ts +63 -0
- package/src/logger/classes/typeorm-logger.ts +83 -0
- package/src/logger/index.ts +20 -0
- package/src/logger/logger.constants.ts +24 -0
- package/src/logger/logger.interfaces.ts +98 -0
- package/src/logger/logger.module.ts +45 -0
- package/src/logger/logger.providers.ts +140 -0
- package/src/logger/winston.tools.ts +241 -0
- package/src/middlewares/app.middleware.ts +26 -0
- package/src/middlewares/index.ts +1 -0
- package/src/modules/hooks/base-hook.ts +64 -0
- package/src/modules/hooks/decorators/on-hook.decorator.ts +19 -0
- package/src/modules/hooks/hooks.module.ts +10 -0
- package/src/modules/hooks/hooks.service.ts +28 -0
- package/src/modules/hooks/index.ts +11 -0
- package/src/modules/id/id.module.ts +26 -0
- package/src/modules/id/id.service.ts +57 -0
- package/src/modules/id/index.ts +2 -0
- package/src/modules/postgres-pubsub/index.ts +3 -0
- package/src/modules/postgres-pubsub/postgres-pubsub.module.ts +14 -0
- package/src/modules/postgres-pubsub/postgres-pubsub.ts +461 -0
- package/src/pagination/constants.ts +9 -0
- package/src/pagination/cursor/cursor-pagination.exception.ts +16 -0
- package/src/pagination/cursor/cursor-pagination.helpers.ts +145 -0
- package/src/pagination/cursor/cursor-pagination.input.ts +96 -0
- package/src/pagination/cursor/cursor-pagination.types.ts +127 -0
- package/src/pagination/index.ts +9 -0
- package/src/pagination/offset/offset-pagination.exception.ts +15 -0
- package/src/pagination/offset/offset-pagination.helpers.ts +122 -0
- package/src/pagination/offset/offset-pagination.input.ts +30 -0
- package/src/pagination/offset/offset-pagination.types.ts +82 -0
- package/src/pagination/tools.ts +53 -0
- package/src/tools/compose.ts +92 -0
- package/src/tools/convert-to-bigint.ts +27 -0
- package/src/tools/create-list-meta.ts +64 -0
- package/src/tools/define-statuses.ts +15 -0
- package/src/tools/env.ts +139 -0
- package/src/tools/fetch-total-with-query.ts +48 -0
- package/src/tools/generate-entity-id.ts +23 -0
- package/src/tools/get-request-language.ts +13 -0
- package/src/tools/is-object.ts +10 -0
- package/src/tools/postgres/locale-to-pg-collate.ts +21 -0
- package/src/tools/remove-undefined-properties.ts +20 -0
- package/src/tools/request-id.ts +25 -0
- package/src/tools/stringify-opts.ts +20 -0
- package/src/tools/typeorm/add-filter.ts +164 -0
- package/src/tools/typeorm/ensure-inner-join.ts +36 -0
- package/src/tools/typeorm/ensure-left-join.ts +36 -0
- package/src/tools/typeorm/is-alias-already-busy.ts +25 -0
- package/src/tools/wait.ts +26 -0
- package/src/types/express-request.ts +8 -0
- package/src/types/list-mehod-options.ts +32 -0
- package/src/types/list-meta.ts +16 -0
- package/src/types/maybe.ts +2 -0
- package/src/validation/index.ts +1 -0
- package/src/validation/validation-pipe.ts +14 -0
- package/tsconfig.lib.json +9 -0
- package/tsdown.config.ts +15 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArgsType,
|
|
3
|
+
Field, Int,
|
|
4
|
+
} from '@nestjs/graphql';
|
|
5
|
+
import { IsOptional, Min } from 'class-validator';
|
|
6
|
+
|
|
7
|
+
@ArgsType()
|
|
8
|
+
export class CursorPaginationInput {
|
|
9
|
+
/**
|
|
10
|
+
* The amount of items to be requested per page from the start
|
|
11
|
+
* @example
|
|
12
|
+
* ```graphql
|
|
13
|
+
* query {
|
|
14
|
+
* messages(first: 3) {
|
|
15
|
+
* edges {
|
|
16
|
+
* cursor
|
|
17
|
+
* node {
|
|
18
|
+
* id
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
@Field(() => Int, {
|
|
26
|
+
nullable: true,
|
|
27
|
+
})
|
|
28
|
+
@Min(1)
|
|
29
|
+
@IsOptional()
|
|
30
|
+
first?: number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The cursor to start the pagination
|
|
34
|
+
* @example
|
|
35
|
+
* ```graphql
|
|
36
|
+
* query {
|
|
37
|
+
* messages(first: 3, after: "xxx") {
|
|
38
|
+
* edges {
|
|
39
|
+
* cursor
|
|
40
|
+
* node {
|
|
41
|
+
* id
|
|
42
|
+
* }
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
@Field(() => String, {
|
|
49
|
+
nullable: true,
|
|
50
|
+
})
|
|
51
|
+
after?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The amount of items to be requested per page from the end
|
|
55
|
+
* @example
|
|
56
|
+
* ```graphql
|
|
57
|
+
* query {
|
|
58
|
+
* messages(last: 2) {
|
|
59
|
+
* edges {
|
|
60
|
+
* cursor
|
|
61
|
+
* node {
|
|
62
|
+
* id
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
* }
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
@Field(() => Int, {
|
|
70
|
+
nullable: true,
|
|
71
|
+
})
|
|
72
|
+
@Min(1)
|
|
73
|
+
@IsOptional()
|
|
74
|
+
last?: number;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The cursor to end the pagination
|
|
78
|
+
* @example
|
|
79
|
+
* ```graphql
|
|
80
|
+
* query {
|
|
81
|
+
* messages(last: 2, before: "xxx") {
|
|
82
|
+
* edges {
|
|
83
|
+
* cursor
|
|
84
|
+
* node {
|
|
85
|
+
* id
|
|
86
|
+
* }
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
* }
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
@Field(() => String, {
|
|
93
|
+
nullable: true,
|
|
94
|
+
})
|
|
95
|
+
before?: string;
|
|
96
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common';
|
|
2
|
+
import { Field, ObjectType } from '@nestjs/graphql';
|
|
3
|
+
|
|
4
|
+
import type { MaybeNull } from '#/types/maybe';
|
|
5
|
+
|
|
6
|
+
@ObjectType()
|
|
7
|
+
export class CursorPaginationPageInfo {
|
|
8
|
+
/**
|
|
9
|
+
* The cursor to the first item in the list
|
|
10
|
+
*/
|
|
11
|
+
@Field(() => String, {
|
|
12
|
+
nullable: true,
|
|
13
|
+
})
|
|
14
|
+
startCursor!: MaybeNull<string>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The cursor to the last item in the list
|
|
18
|
+
*/
|
|
19
|
+
@Field(() => String, {
|
|
20
|
+
nullable: true,
|
|
21
|
+
})
|
|
22
|
+
endCursor!: MaybeNull<string>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Whether there are more items in the list before the start cursor
|
|
26
|
+
*/
|
|
27
|
+
@Field(() => Boolean, {
|
|
28
|
+
nullable: true,
|
|
29
|
+
})
|
|
30
|
+
hasPreviousPage?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether there are more items in the list after the end cursor
|
|
34
|
+
*/
|
|
35
|
+
@Field(() => Boolean, {
|
|
36
|
+
nullable: true,
|
|
37
|
+
})
|
|
38
|
+
hasNextPage?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The edge type for cursor pagination
|
|
43
|
+
*/
|
|
44
|
+
export interface IEdge<T> {
|
|
45
|
+
/**
|
|
46
|
+
* The cursor for the item
|
|
47
|
+
*/
|
|
48
|
+
cursor: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The item
|
|
52
|
+
*/
|
|
53
|
+
node: T;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ICursorPaginated<T> {
|
|
57
|
+
/**
|
|
58
|
+
* The list of edges
|
|
59
|
+
*/
|
|
60
|
+
edges: IEdge<T>[];
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The pagination info
|
|
64
|
+
*/
|
|
65
|
+
pageInfo: CursorPaginationPageInfo;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Creates a new GraphQL object type with the name `Paginated${classRef.name}`
|
|
70
|
+
* that implements the `ICursorPaginated` interface
|
|
71
|
+
* @param classRef The class reference of the items
|
|
72
|
+
*
|
|
73
|
+
* @example Create PaginatedMessages type
|
|
74
|
+
* ```ts
|
|
75
|
+
* @ObjectType()
|
|
76
|
+
* class PaginatedMessages extends CursorPaginated(Message) {}
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
// eslint-disable-next-line func-style
|
|
80
|
+
export function CursorPaginated<T>(classRef: Type<T>): Type<ICursorPaginated<T>> {
|
|
81
|
+
@ObjectType(`${classRef.name}Edge`)
|
|
82
|
+
abstract class EdgeType implements IEdge<T> {
|
|
83
|
+
/**
|
|
84
|
+
* The cursor for the item
|
|
85
|
+
*/
|
|
86
|
+
@Field(() => String, {
|
|
87
|
+
description: 'The cursor for the item',
|
|
88
|
+
})
|
|
89
|
+
cursor!: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The item
|
|
93
|
+
*/
|
|
94
|
+
@Field(() => classRef, {
|
|
95
|
+
description: 'The item',
|
|
96
|
+
})
|
|
97
|
+
node!: T;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@ObjectType({
|
|
101
|
+
isAbstract: true,
|
|
102
|
+
})
|
|
103
|
+
abstract class CursorPaginatedType implements ICursorPaginated<T> {
|
|
104
|
+
/**
|
|
105
|
+
* The list of edges
|
|
106
|
+
*/
|
|
107
|
+
@Field(() => [EdgeType], {
|
|
108
|
+
description: 'The list of edges',
|
|
109
|
+
})
|
|
110
|
+
edges!: EdgeType[];
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The pagination info
|
|
114
|
+
*/
|
|
115
|
+
@Field(() => CursorPaginationPageInfo, {
|
|
116
|
+
description: 'The pagination info',
|
|
117
|
+
})
|
|
118
|
+
pageInfo!: CursorPaginationPageInfo;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return CursorPaginatedType as Type<ICursorPaginated<T>>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export enum CursorOrderBy {
|
|
125
|
+
createdAt_ASC = 'createdAt_ASC',
|
|
126
|
+
createdAt_DESC = 'createdAt_DESC',
|
|
127
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './cursor/cursor-pagination.helpers.js';
|
|
2
|
+
export * from './cursor/cursor-pagination.input.js';
|
|
3
|
+
export * from './cursor/cursor-pagination.types.js';
|
|
4
|
+
|
|
5
|
+
export * from './offset/offset-pagination.helpers.js';
|
|
6
|
+
export * from './offset/offset-pagination.input.js';
|
|
7
|
+
export * from './offset/offset-pagination.types.js';
|
|
8
|
+
|
|
9
|
+
export * from './tools.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NestError } from '#/errors';
|
|
2
|
+
|
|
3
|
+
export enum OffsetErrorMessage {
|
|
4
|
+
OUTPUT_LIMIT = `Can't build page meta. Invalid limit`,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class OffsetPaginationError extends NestError {
|
|
8
|
+
constructor(public readonly message: OffsetErrorMessage) {
|
|
9
|
+
super({
|
|
10
|
+
message,
|
|
11
|
+
key: 'NST_INVALID_OFFSET_PAGINATION',
|
|
12
|
+
});
|
|
13
|
+
this.name = 'OffsetPaginationError';
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { snakeCase } from '@pcg/text-kit';
|
|
2
|
+
import { GraphQLResolveInfo } from 'graphql';
|
|
3
|
+
|
|
4
|
+
import { ListMeta } from '#/types/list-meta';
|
|
5
|
+
|
|
6
|
+
import { PAGINATION_DEFAULT_LIMIT } from '../constants.js';
|
|
7
|
+
import { requiresTotalCount, truncPaginationLimit } from '../tools.js';
|
|
8
|
+
import { OffsetErrorMessage, OffsetPaginationError } from './offset-pagination.exception.js';
|
|
9
|
+
import { OffsetPaginationInput } from './offset-pagination.input.js';
|
|
10
|
+
import { IOffsetPaginated } from './offset-pagination.types.js';
|
|
11
|
+
|
|
12
|
+
export type SortOrder = 'ASC' | 'DESC';
|
|
13
|
+
|
|
14
|
+
export interface SortParams<T> {
|
|
15
|
+
/**
|
|
16
|
+
* Original sort field name.
|
|
17
|
+
* @example
|
|
18
|
+
* const orderBy = 'createdAt_ASC';
|
|
19
|
+
* const sort = extractSortParams(orderBy); // sort.fieldName = 'uploaderName';
|
|
20
|
+
* if (sort.fieldName === uploaderName) {
|
|
21
|
+
* ...
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
fieldName: T;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Database column name. Camel cased fieldName.
|
|
28
|
+
* @example
|
|
29
|
+
* const orderBy = 'createdAt_ASC';
|
|
30
|
+
* const sort = extractSortParams(orderBy); // sort.columnName = 'created_at';
|
|
31
|
+
* query.orderBy(`v.${sort.columnName}`, sort.direction);
|
|
32
|
+
*/
|
|
33
|
+
columnName: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Sort direction (or order) (ASC | DESC)
|
|
37
|
+
* @example
|
|
38
|
+
* const orderBy = 'createdAt_ASC';
|
|
39
|
+
* const sort = extractSortParams(orderBy); // sort.direction = 'ASC';
|
|
40
|
+
* query.orderBy(`v.${sort.columnName}`, sort.direction);
|
|
41
|
+
*/
|
|
42
|
+
direction: SortOrder;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Extract sort field name and sort direction from orderBy string.
|
|
47
|
+
* @example
|
|
48
|
+
* const orderBy = 'createdAt_ASC';
|
|
49
|
+
*
|
|
50
|
+
* // sort.fieldName = 'createdAt', sort.direction = 'ASC'; sort.columnName = 'created_at';
|
|
51
|
+
* const sort = extractSortParams(orderBy);
|
|
52
|
+
*/
|
|
53
|
+
export const extractSortParams = <T>(orderBy: string): SortParams<T> => {
|
|
54
|
+
const slices = orderBy.split('_');
|
|
55
|
+
|
|
56
|
+
if (slices.length !== 2) {
|
|
57
|
+
throw new Error('Invalid orderBy argument');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
fieldName: slices[0] as unknown as T,
|
|
62
|
+
direction: slices[1] as unknown as SortOrder,
|
|
63
|
+
columnName: snakeCase(slices[0]),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export interface OffsetPaginationOptions {
|
|
68
|
+
limit?: number;
|
|
69
|
+
offset?: number;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* If true, total count will be calculated by additional SELECT COUNT.
|
|
73
|
+
* @default true
|
|
74
|
+
*/
|
|
75
|
+
needCountTotal: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface CreateOffsetPaginationOptionsParams {
|
|
79
|
+
maxLimit?: number;
|
|
80
|
+
defaultLimit?: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const createOffsetPaginationOptions = (
|
|
84
|
+
input: OffsetPaginationInput,
|
|
85
|
+
info: GraphQLResolveInfo,
|
|
86
|
+
params?: CreateOffsetPaginationOptionsParams,
|
|
87
|
+
): OffsetPaginationOptions => {
|
|
88
|
+
const options: OffsetPaginationOptions = {
|
|
89
|
+
needCountTotal: requiresTotalCount(info),
|
|
90
|
+
limit: params?.defaultLimit ?? PAGINATION_DEFAULT_LIMIT,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
if (input.limit) {
|
|
94
|
+
options.limit = truncPaginationLimit(Number(input.limit), params?.maxLimit);
|
|
95
|
+
|
|
96
|
+
if (input.page) {
|
|
97
|
+
options.offset = (Number(input.page) - 1) * options.limit;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return options;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const offsetPaginatedOutput = <T>(items: T[], {
|
|
105
|
+
limit,
|
|
106
|
+
offset = 0,
|
|
107
|
+
total = 0,
|
|
108
|
+
}: ListMeta): IOffsetPaginated<T> => {
|
|
109
|
+
if (!limit) {
|
|
110
|
+
throw new OffsetPaginationError(OffsetErrorMessage.OUTPUT_LIMIT);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
items,
|
|
115
|
+
pageInfo: {
|
|
116
|
+
totalPages: Math.ceil(total / limit),
|
|
117
|
+
totalItems: total,
|
|
118
|
+
page: (offset / limit) + 1,
|
|
119
|
+
limit,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArgsType,
|
|
3
|
+
Field, Int,
|
|
4
|
+
} from '@nestjs/graphql';
|
|
5
|
+
|
|
6
|
+
import { PAGINATION_DEFAULT_LIMIT } from '../constants.js';
|
|
7
|
+
|
|
8
|
+
@ArgsType()
|
|
9
|
+
export class OffsetPaginationInput {
|
|
10
|
+
/**
|
|
11
|
+
* the amount of items to be requested per page
|
|
12
|
+
*/
|
|
13
|
+
@Field(() => Int, {
|
|
14
|
+
description: 'the amount of items to be requested per page',
|
|
15
|
+
defaultValue: PAGINATION_DEFAULT_LIMIT,
|
|
16
|
+
nullable: true,
|
|
17
|
+
})
|
|
18
|
+
limit?: number;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @default 1
|
|
22
|
+
* the page that is requested
|
|
23
|
+
*/
|
|
24
|
+
@Field(() => Int, {
|
|
25
|
+
defaultValue: 1,
|
|
26
|
+
description: 'the page that is requested',
|
|
27
|
+
})
|
|
28
|
+
page?: number = 1;
|
|
29
|
+
}
|
|
30
|
+
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Type } from '@nestjs/common';
|
|
2
|
+
import {
|
|
3
|
+
Field, Int,
|
|
4
|
+
ObjectType,
|
|
5
|
+
} from '@nestjs/graphql';
|
|
6
|
+
|
|
7
|
+
@ObjectType()
|
|
8
|
+
export class OffsetPaginationPageInfo {
|
|
9
|
+
/**
|
|
10
|
+
* The total amount of pages
|
|
11
|
+
* (total items / limit)
|
|
12
|
+
*/
|
|
13
|
+
@Field(() => Int, {
|
|
14
|
+
description: 'The total amount of pages (total items / limit)',
|
|
15
|
+
})
|
|
16
|
+
totalPages!: number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The total amount of items
|
|
20
|
+
*/
|
|
21
|
+
@Field(() => Int, {
|
|
22
|
+
description: 'The total amount of items',
|
|
23
|
+
})
|
|
24
|
+
totalItems!: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The current page
|
|
28
|
+
*/
|
|
29
|
+
@Field(() => Int, {
|
|
30
|
+
description: 'The current page',
|
|
31
|
+
})
|
|
32
|
+
page!: number;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The amount of items to be requested per page
|
|
36
|
+
*/
|
|
37
|
+
@Field(() => Int, {
|
|
38
|
+
description: 'The amount of items to be requested per page',
|
|
39
|
+
})
|
|
40
|
+
limit!: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IOffsetPaginated<T> {
|
|
44
|
+
items: T[];
|
|
45
|
+
pageInfo: OffsetPaginationPageInfo;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a new GraphQL object type with the name `Paginated${classRef.name}`
|
|
50
|
+
* that implements the `IOffsetPaginated` interface
|
|
51
|
+
* @param classRef The class reference of the items
|
|
52
|
+
*
|
|
53
|
+
* @example Create PaginatedUsers type
|
|
54
|
+
* ```ts
|
|
55
|
+
* @ObjectType()
|
|
56
|
+
* class PaginatedUsers extends OffsetPaginated(User) {}
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
// eslint-disable-next-line func-style
|
|
60
|
+
export function OffsetPaginated<T>(classRef: Type<T>): Type<IOffsetPaginated<T>> {
|
|
61
|
+
@ObjectType({
|
|
62
|
+
isAbstract: true,
|
|
63
|
+
})
|
|
64
|
+
abstract class OffsetPaginatedType implements IOffsetPaginated<T> {
|
|
65
|
+
/**
|
|
66
|
+
* The items of the current page
|
|
67
|
+
*/
|
|
68
|
+
@Field(() => [classRef], {
|
|
69
|
+
description: 'The items of the current page',
|
|
70
|
+
})
|
|
71
|
+
items!: T[];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The pagination information
|
|
75
|
+
* (total pages, total items, current page, limit)
|
|
76
|
+
*/
|
|
77
|
+
@Field(() => OffsetPaginationPageInfo)
|
|
78
|
+
pageInfo!: OffsetPaginationPageInfo;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return OffsetPaginatedType as Type<IOffsetPaginated<T>>;
|
|
82
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { GraphQLResolveInfo } from 'graphql';
|
|
2
|
+
|
|
3
|
+
import { PAGINATION_MAX_LIMIT } from './constants.js';
|
|
4
|
+
|
|
5
|
+
export const truncPaginationLimit = (limit: number, maxLimit = PAGINATION_MAX_LIMIT) =>
|
|
6
|
+
Math.min(limit, maxLimit);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Determines whether the GraphQL query requires a total count of items.
|
|
10
|
+
* @param info - The GraphQLResolveInfo object containing information about the query.
|
|
11
|
+
* @returns A boolean indicating whether the query requires a total count of items.
|
|
12
|
+
*/
|
|
13
|
+
export const requiresTotalCount = (info: GraphQLResolveInfo): boolean => {
|
|
14
|
+
const fieldNode = info.fieldNodes[0];
|
|
15
|
+
|
|
16
|
+
if (!fieldNode.selectionSet) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
for (const selection of fieldNode.selectionSet.selections) {
|
|
21
|
+
if (selection.kind === 'Field' && selection.name.value === 'pageInfo') {
|
|
22
|
+
if (selection.selectionSet) {
|
|
23
|
+
for (const pageInfoSelection of selection.selectionSet.selections) {
|
|
24
|
+
if (
|
|
25
|
+
pageInfoSelection.kind === 'Field' &&
|
|
26
|
+
(pageInfoSelection.name.value === 'totalPages' || pageInfoSelection.name.value === 'totalItems')
|
|
27
|
+
) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (info.fragments) {
|
|
36
|
+
const fragments = Object.values(info.fragments);
|
|
37
|
+
for (const fragment of fragments) {
|
|
38
|
+
// const regex = new RegExp('/OffsetPagination|PageInfo/i');
|
|
39
|
+
if (fragment.selectionSet) {
|
|
40
|
+
for (const pageInfoSelection of fragment.selectionSet.selections) {
|
|
41
|
+
if (
|
|
42
|
+
pageInfoSelection.kind === 'Field' &&
|
|
43
|
+
(pageInfoSelection.name.value === 'totalPages' || pageInfoSelection.name.value === 'totalItems')
|
|
44
|
+
) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
export type Constructor = new (...args: any[]) => any
|
|
3
|
+
|
|
4
|
+
type MixinFunction<TInput, TOutput> = (source: TInput) => TOutput
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Compose a class by applying mixins to it.
|
|
8
|
+
* The code is inspired by https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/, its
|
|
9
|
+
* just that I have added the support for static types too.
|
|
10
|
+
*
|
|
11
|
+
* @param baseClass - The base class to extend
|
|
12
|
+
* @param mixins - One or more mixin functions to apply sequentially
|
|
13
|
+
* @returns The composed class with all mixins applied
|
|
14
|
+
*/
|
|
15
|
+
export function compose<TBase extends Constructor, TResult1>(
|
|
16
|
+
baseClass: TBase,
|
|
17
|
+
mixin1: MixinFunction<TBase, TResult1>
|
|
18
|
+
): TResult1
|
|
19
|
+
export function compose<TBase extends Constructor, TResult1, TResult2>(
|
|
20
|
+
baseClass: TBase,
|
|
21
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
22
|
+
mixin2: MixinFunction<TResult1, TResult2>
|
|
23
|
+
): TResult2
|
|
24
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3>(
|
|
25
|
+
baseClass: TBase,
|
|
26
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
27
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
28
|
+
mixin3: MixinFunction<TResult2, TResult3>
|
|
29
|
+
): TResult3
|
|
30
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4>(
|
|
31
|
+
baseClass: TBase,
|
|
32
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
33
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
34
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
35
|
+
mixin4: MixinFunction<TResult3, TResult4>
|
|
36
|
+
): TResult4
|
|
37
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4, TResult5>(
|
|
38
|
+
baseClass: TBase,
|
|
39
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
40
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
41
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
42
|
+
mixin4: MixinFunction<TResult3, TResult4>,
|
|
43
|
+
mixin5: MixinFunction<TResult4, TResult5>
|
|
44
|
+
): TResult5
|
|
45
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4, TResult5, TResult6>(
|
|
46
|
+
baseClass: TBase,
|
|
47
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
48
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
49
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
50
|
+
mixin4: MixinFunction<TResult3, TResult4>,
|
|
51
|
+
mixin5: MixinFunction<TResult4, TResult5>,
|
|
52
|
+
mixin6: MixinFunction<TResult5, TResult6>
|
|
53
|
+
): TResult6
|
|
54
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4, TResult5, TResult6, TResult7>(
|
|
55
|
+
baseClass: TBase,
|
|
56
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
57
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
58
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
59
|
+
mixin4: MixinFunction<TResult3, TResult4>,
|
|
60
|
+
mixin5: MixinFunction<TResult4, TResult5>,
|
|
61
|
+
mixin6: MixinFunction<TResult5, TResult6>,
|
|
62
|
+
mixin7: MixinFunction<TResult6, TResult7>
|
|
63
|
+
): TResult7
|
|
64
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4, TResult5, TResult6, TResult7, TResult8>(
|
|
65
|
+
baseClass: TBase,
|
|
66
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
67
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
68
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
69
|
+
mixin4: MixinFunction<TResult3, TResult4>,
|
|
70
|
+
mixin5: MixinFunction<TResult4, TResult5>,
|
|
71
|
+
mixin6: MixinFunction<TResult5, TResult6>,
|
|
72
|
+
mixin7: MixinFunction<TResult6, TResult7>,
|
|
73
|
+
mixin8: MixinFunction<TResult7, TResult8>
|
|
74
|
+
): TResult8
|
|
75
|
+
export function compose<TBase extends Constructor, TResult1, TResult2, TResult3, TResult4, TResult5, TResult6, TResult7, TResult8, TResult9>(
|
|
76
|
+
baseClass: TBase,
|
|
77
|
+
mixin1: MixinFunction<TBase, TResult1>,
|
|
78
|
+
mixin2: MixinFunction<TResult1, TResult2>,
|
|
79
|
+
mixin3: MixinFunction<TResult2, TResult3>,
|
|
80
|
+
mixin4: MixinFunction<TResult3, TResult4>,
|
|
81
|
+
mixin5: MixinFunction<TResult4, TResult5>,
|
|
82
|
+
mixin6: MixinFunction<TResult5, TResult6>,
|
|
83
|
+
mixin7: MixinFunction<TResult6, TResult7>,
|
|
84
|
+
mixin8: MixinFunction<TResult7, TResult8>,
|
|
85
|
+
mixin9: MixinFunction<TResult8, TResult9>
|
|
86
|
+
): TResult9
|
|
87
|
+
export function compose<TBase extends Constructor, TMixin extends MixinFunction<TBase, TBase>>(
|
|
88
|
+
baseClass: TBase,
|
|
89
|
+
...mixins: TMixin[]
|
|
90
|
+
) {
|
|
91
|
+
return mixins.reduce((currentClass, mixin) => mixin(currentClass), baseClass);
|
|
92
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a number or string value to a BigInt, preserving null and undefined values.
|
|
3
|
+
*
|
|
4
|
+
* @param value - The value to convert. Can be a number, string, undefined, or null.
|
|
5
|
+
* @returns The converted BigInt value, or the original value if it was null or undefined.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* convertToBigInt(123) // returns 123n
|
|
10
|
+
* convertToBigInt("456") // returns 456n
|
|
11
|
+
* convertToBigInt(null) // returns null
|
|
12
|
+
* convertToBigInt(undefined) // returns undefined
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @throws {RangeError} Throws when the input cannot be converted to a valid BigInt.
|
|
16
|
+
*/
|
|
17
|
+
export const convertToBigInt = (value: number | string | undefined | null): bigint | undefined | null => {
|
|
18
|
+
if (value === null) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof value === 'undefined') {
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return BigInt(value);
|
|
27
|
+
};
|