@lenne.tech/nest-server 11.10.5 → 11.11.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/dist/core/common/models/pagination-info.model.d.ts +15 -0
- package/dist/core/common/models/pagination-info.model.js +84 -0
- package/dist/core/common/models/pagination-info.model.js.map +1 -0
- package/dist/core/common/services/crud.service.d.ts +4 -0
- package/dist/core/common/services/crud.service.js +9 -0
- package/dist/core/common/services/crud.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.d.ts +2 -0
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.js +9 -0
- package/dist/server/modules/user/outputs/find-and-count-users-result.output.js.map +1 -1
- package/dist/server/modules/user/user.resolver.d.ts +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +31 -28
- package/src/core/common/models/pagination-info.model.ts +101 -0
- package/src/core/common/services/crud.service.ts +19 -3
- package/src/index.ts +1 -0
- package/src/server/modules/user/outputs/find-and-count-users-result.output.ts +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lenne.tech/nest-server",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.11.0",
|
|
4
4
|
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node",
|
|
@@ -78,27 +78,27 @@
|
|
|
78
78
|
"node": ">= 20"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@apollo/server": "5.
|
|
81
|
+
"@apollo/server": "5.3.0",
|
|
82
82
|
"@as-integrations/express5": "1.1.2",
|
|
83
|
-
"@better-auth/passkey": "1.4.
|
|
83
|
+
"@better-auth/passkey": "1.4.17",
|
|
84
84
|
"@getbrevo/brevo": "3.0.1",
|
|
85
85
|
"@nestjs/apollo": "13.2.3",
|
|
86
|
-
"@nestjs/common": "11.1.
|
|
87
|
-
"@nestjs/core": "11.1.
|
|
86
|
+
"@nestjs/common": "11.1.12",
|
|
87
|
+
"@nestjs/core": "11.1.12",
|
|
88
88
|
"@nestjs/graphql": "13.2.3",
|
|
89
89
|
"@nestjs/jwt": "11.0.2",
|
|
90
90
|
"@nestjs/mongoose": "11.0.4",
|
|
91
91
|
"@nestjs/passport": "11.0.5",
|
|
92
|
-
"@nestjs/platform-express": "11.1.
|
|
92
|
+
"@nestjs/platform-express": "11.1.12",
|
|
93
93
|
"@nestjs/schedule": "6.1.0",
|
|
94
|
-
"@nestjs/swagger": "11.2.
|
|
94
|
+
"@nestjs/swagger": "11.2.5",
|
|
95
95
|
"@nestjs/terminus": "11.0.0",
|
|
96
|
-
"@nestjs/websockets": "11.1.
|
|
96
|
+
"@nestjs/websockets": "11.1.12",
|
|
97
97
|
"@tus/file-store": "2.0.0",
|
|
98
98
|
"@tus/server": "2.3.0",
|
|
99
99
|
"apollo-server-core": "3.13.0",
|
|
100
100
|
"bcrypt": "6.0.0",
|
|
101
|
-
"better-auth": "1.4.
|
|
101
|
+
"better-auth": "1.4.17",
|
|
102
102
|
"class-transformer": "0.5.1",
|
|
103
103
|
"class-validator": "0.14.3",
|
|
104
104
|
"compression": "1.8.1",
|
|
@@ -112,12 +112,12 @@
|
|
|
112
112
|
"graphql-upload": "15.0.2",
|
|
113
113
|
"js-sha256": "0.11.1",
|
|
114
114
|
"json-to-graphql-query": "2.3.0",
|
|
115
|
-
"lodash": "4.17.
|
|
115
|
+
"lodash": "4.17.23",
|
|
116
116
|
"mongodb": "7.0.0",
|
|
117
|
-
"mongoose": "9.
|
|
117
|
+
"mongoose": "9.1.5",
|
|
118
118
|
"multer": "2.0.2",
|
|
119
119
|
"node-mailjet": "6.0.11",
|
|
120
|
-
"nodemailer": "7.0.
|
|
120
|
+
"nodemailer": "7.0.12",
|
|
121
121
|
"passport": "0.7.0",
|
|
122
122
|
"passport-jwt": "4.0.1",
|
|
123
123
|
"reflect-metadata": "0.2.2",
|
|
@@ -126,28 +126,27 @@
|
|
|
126
126
|
"yuml-diagram": "1.2.0"
|
|
127
127
|
},
|
|
128
128
|
"devDependencies": {
|
|
129
|
-
"@compodoc/compodoc": "1.1
|
|
129
|
+
"@compodoc/compodoc": "1.2.1",
|
|
130
130
|
"@lenne.tech/eslint-config-ts": "2.1.4",
|
|
131
|
-
"@nestjs/cli": "11.0.
|
|
131
|
+
"@nestjs/cli": "11.0.16",
|
|
132
132
|
"@nestjs/schematics": "11.0.9",
|
|
133
|
-
"@nestjs/testing": "11.1.
|
|
133
|
+
"@nestjs/testing": "11.1.12",
|
|
134
134
|
"@swc/cli": "0.7.9",
|
|
135
|
-
"@swc/core": "1.15.
|
|
135
|
+
"@swc/core": "1.15.10",
|
|
136
136
|
"@types/compression": "1.8.1",
|
|
137
137
|
"@types/cookie-parser": "1.4.10",
|
|
138
138
|
"@types/ejs": "3.1.5",
|
|
139
139
|
"@types/express": "4.17.21",
|
|
140
|
-
"@types/lodash": "4.17.
|
|
140
|
+
"@types/lodash": "4.17.23",
|
|
141
141
|
"@types/multer": "2.0.0",
|
|
142
|
-
"@types/node": "25.0.
|
|
143
|
-
"@types/nodemailer": "7.0.
|
|
142
|
+
"@types/node": "25.0.10",
|
|
143
|
+
"@types/nodemailer": "7.0.5",
|
|
144
144
|
"@types/passport": "1.0.17",
|
|
145
145
|
"@types/supertest": "6.0.3",
|
|
146
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
147
|
-
"@typescript-eslint/parser": "8.
|
|
148
|
-
"@vitest/coverage-v8": "4.0.
|
|
149
|
-
"@vitest/ui": "4.0.
|
|
150
|
-
"otpauth": "9.4.1",
|
|
146
|
+
"@typescript-eslint/eslint-plugin": "8.53.1",
|
|
147
|
+
"@typescript-eslint/parser": "8.53.1",
|
|
148
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
149
|
+
"@vitest/ui": "4.0.18",
|
|
151
150
|
"ansi-colors": "4.1.3",
|
|
152
151
|
"eslint": "9.39.2",
|
|
153
152
|
"eslint-config-prettier": "10.1.8",
|
|
@@ -161,11 +160,12 @@
|
|
|
161
160
|
"husky": "9.1.7",
|
|
162
161
|
"nodemon": "3.1.11",
|
|
163
162
|
"npm-watch": "0.13.0",
|
|
163
|
+
"otpauth": "9.4.1",
|
|
164
164
|
"pm2": "6.0.14",
|
|
165
165
|
"prettier": "3.7.4",
|
|
166
166
|
"pretty-quick": "4.2.2",
|
|
167
167
|
"rimraf": "6.1.2",
|
|
168
|
-
"supertest": "7.
|
|
168
|
+
"supertest": "7.2.2",
|
|
169
169
|
"ts-jest": "29.4.6",
|
|
170
170
|
"ts-loader": "9.5.4",
|
|
171
171
|
"ts-morph": "27.0.2",
|
|
@@ -174,10 +174,10 @@
|
|
|
174
174
|
"tus-js-client": "4.3.1",
|
|
175
175
|
"typescript": "5.9.3",
|
|
176
176
|
"unplugin-swc": "1.5.9",
|
|
177
|
-
"vite": "7.3.
|
|
177
|
+
"vite": "7.3.1",
|
|
178
178
|
"vite-plugin-node": "7.0.0",
|
|
179
|
-
"vite-tsconfig-paths": "6.0.
|
|
180
|
-
"vitest": "4.0.
|
|
179
|
+
"vite-tsconfig-paths": "6.0.5",
|
|
180
|
+
"vitest": "4.0.18",
|
|
181
181
|
"yalc": "1.0.0-pre.53"
|
|
182
182
|
},
|
|
183
183
|
"jest": {
|
|
@@ -208,5 +208,8 @@
|
|
|
208
208
|
],
|
|
209
209
|
"watch": {
|
|
210
210
|
"build:dev": "src"
|
|
211
|
+
},
|
|
212
|
+
"overrides": {
|
|
213
|
+
"lodash": "4.17.23"
|
|
211
214
|
}
|
|
212
215
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ObjectType } from '@nestjs/graphql';
|
|
2
|
+
|
|
3
|
+
import { UnifiedField } from '../decorators/unified-field.decorator';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pagination information for paginated queries
|
|
7
|
+
*
|
|
8
|
+
* Provides metadata about the current page, total pages, and navigation flags
|
|
9
|
+
* to facilitate frontend pagination UI implementation.
|
|
10
|
+
*/
|
|
11
|
+
@ObjectType({ description: 'Pagination information for paginated queries' })
|
|
12
|
+
export class PaginationInfo {
|
|
13
|
+
/**
|
|
14
|
+
* Total number of items across all pages
|
|
15
|
+
*/
|
|
16
|
+
@UnifiedField({
|
|
17
|
+
description: 'Total number of items across all pages',
|
|
18
|
+
})
|
|
19
|
+
totalCount: number;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Total number of pages
|
|
23
|
+
*/
|
|
24
|
+
@UnifiedField({
|
|
25
|
+
description: 'Total number of pages',
|
|
26
|
+
})
|
|
27
|
+
pageCount: number;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Current page number (1-based)
|
|
31
|
+
*/
|
|
32
|
+
@UnifiedField({
|
|
33
|
+
description: 'Current page number (1-based)',
|
|
34
|
+
})
|
|
35
|
+
currentPage: number;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Number of items per page
|
|
39
|
+
*/
|
|
40
|
+
@UnifiedField({
|
|
41
|
+
description: 'Number of items per page',
|
|
42
|
+
})
|
|
43
|
+
perPage: number;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Indicates if there is a next page
|
|
47
|
+
*/
|
|
48
|
+
@UnifiedField({
|
|
49
|
+
description: 'Indicates if there is a next page',
|
|
50
|
+
})
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Indicates if there is a previous page
|
|
55
|
+
*/
|
|
56
|
+
@UnifiedField({
|
|
57
|
+
description: 'Indicates if there is a previous page',
|
|
58
|
+
})
|
|
59
|
+
hasPreviousPage: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create PaginationInfo from query parameters and total count
|
|
63
|
+
*/
|
|
64
|
+
static create(options: {
|
|
65
|
+
limit?: number;
|
|
66
|
+
offset?: number;
|
|
67
|
+
skip?: number;
|
|
68
|
+
take?: number;
|
|
69
|
+
totalCount: number;
|
|
70
|
+
}): PaginationInfo {
|
|
71
|
+
const { totalCount } = options;
|
|
72
|
+
const skip = options.skip ?? options.offset ?? 0;
|
|
73
|
+
const perPage = options.take ?? options.limit ?? 0;
|
|
74
|
+
|
|
75
|
+
// Handle edge case: no limit specified (return all items as single page)
|
|
76
|
+
if (perPage === 0) {
|
|
77
|
+
return {
|
|
78
|
+
currentPage: totalCount > 0 ? 1 : 0,
|
|
79
|
+
hasNextPage: false,
|
|
80
|
+
hasPreviousPage: false,
|
|
81
|
+
pageCount: totalCount > 0 ? 1 : 0,
|
|
82
|
+
perPage: 0,
|
|
83
|
+
totalCount,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const pageCount = Math.ceil(totalCount / perPage);
|
|
88
|
+
const currentPage = totalCount === 0 ? 0 : Math.floor(skip / perPage) + 1;
|
|
89
|
+
const hasNextPage = currentPage < pageCount;
|
|
90
|
+
const hasPreviousPage = currentPage > 1;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
currentPage,
|
|
94
|
+
hasNextPage,
|
|
95
|
+
hasPreviousPage,
|
|
96
|
+
pageCount,
|
|
97
|
+
perPage,
|
|
98
|
+
totalCount,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -15,6 +15,7 @@ import { convertFilterArgsToQuery } from '../helpers/filter.helper';
|
|
|
15
15
|
import { mergePlain, prepareServiceOptionsForCreate } from '../helpers/input.helper';
|
|
16
16
|
import { ServiceOptions } from '../interfaces/service-options.interface';
|
|
17
17
|
import { CoreModel } from '../models/core-model.model';
|
|
18
|
+
import { PaginationInfo } from '../models/pagination-info.model';
|
|
18
19
|
import { PlainObject } from '../types/plain-object.type';
|
|
19
20
|
import { ConfigService } from './config.service';
|
|
20
21
|
import { ModuleService } from './module.service';
|
|
@@ -218,7 +219,11 @@ export abstract class CrudService<
|
|
|
218
219
|
async findAndCount(
|
|
219
220
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
220
221
|
serviceOptions?: ServiceOptions,
|
|
221
|
-
): Promise<{
|
|
222
|
+
): Promise<{
|
|
223
|
+
items: Model[];
|
|
224
|
+
pagination: PaginationInfo;
|
|
225
|
+
totalCount: number;
|
|
226
|
+
}> {
|
|
222
227
|
// If filter is not instance of FilterArgs a simple form with filterQuery and queryOptions is set
|
|
223
228
|
// and should not be processed as FilterArgs
|
|
224
229
|
if (!(filter instanceof FilterArgs) && serviceOptions?.inputType === FilterArgs) {
|
|
@@ -284,6 +289,17 @@ export abstract class CrudService<
|
|
|
284
289
|
(await this.mainDbModel.aggregate(aggregation, collation ? { collation } : {}).exec())[0] || {};
|
|
285
290
|
dbResult.totalCount = dbResult.totalCount?.[0]?.total || 0;
|
|
286
291
|
dbResult.items = dbResult.items?.map((item) => this.mainDbModel.hydrate(item)) || [];
|
|
292
|
+
|
|
293
|
+
// Calculate pagination info
|
|
294
|
+
const queryOptions = filterQuery.queryOptions || {};
|
|
295
|
+
dbResult.pagination = PaginationInfo.create({
|
|
296
|
+
limit: queryOptions.limit,
|
|
297
|
+
offset: queryOptions.offset,
|
|
298
|
+
skip: queryOptions.skip,
|
|
299
|
+
take: queryOptions.take,
|
|
300
|
+
totalCount: dbResult.totalCount,
|
|
301
|
+
});
|
|
302
|
+
|
|
287
303
|
return dbResult;
|
|
288
304
|
},
|
|
289
305
|
{ input: filter, outputPath: 'items', serviceOptions },
|
|
@@ -297,7 +313,7 @@ export abstract class CrudService<
|
|
|
297
313
|
async findAndCountForce(
|
|
298
314
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
299
315
|
serviceOptions: ServiceOptions = {},
|
|
300
|
-
): Promise<{ items: Model[]; totalCount: number }> {
|
|
316
|
+
): Promise<{ items: Model[]; pagination: PaginationInfo; totalCount: number; }> {
|
|
301
317
|
serviceOptions.raw = true;
|
|
302
318
|
return this.findAndCount(filter, serviceOptions);
|
|
303
319
|
}
|
|
@@ -309,7 +325,7 @@ export abstract class CrudService<
|
|
|
309
325
|
async findAndCountRaw(
|
|
310
326
|
filter?: FilterArgs | { filterQuery?: QueryFilter<any>; queryOptions?: QueryOptions; samples?: number },
|
|
311
327
|
serviceOptions: ServiceOptions = {},
|
|
312
|
-
): Promise<{ items: Model[]; totalCount: number }> {
|
|
328
|
+
): Promise<{ items: Model[]; pagination: PaginationInfo; totalCount: number; }> {
|
|
313
329
|
serviceOptions = serviceOptions || {};
|
|
314
330
|
serviceOptions.raw = true;
|
|
315
331
|
return this.findAndCountForce(filter, serviceOptions);
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ export * from './core/common/interfaces/service-options.interface';
|
|
|
62
62
|
export * from './core/common/middlewares/to-lower-case.middleware';
|
|
63
63
|
export * from './core/common/models/core-model.model';
|
|
64
64
|
export * from './core/common/models/core-persistence.model';
|
|
65
|
+
export * from './core/common/models/pagination-info.model';
|
|
65
66
|
export * from './core/common/pipes/check-input.pipe';
|
|
66
67
|
export * from './core/common/pipes/map-and-validate.pipe';
|
|
67
68
|
export * from './core/common/plugins/complexity.plugin';
|
|
@@ -3,6 +3,7 @@ import { ObjectType } from '@nestjs/graphql';
|
|
|
3
3
|
import { Restricted } from '../../../../core/common/decorators/restricted.decorator';
|
|
4
4
|
import { UnifiedField } from '../../../../core/common/decorators/unified-field.decorator';
|
|
5
5
|
import { RoleEnum } from '../../../../core/common/enums/role.enum';
|
|
6
|
+
import { PaginationInfo } from '../../../../core/common/models/pagination-info.model';
|
|
6
7
|
import { User } from '../user.model';
|
|
7
8
|
|
|
8
9
|
@ObjectType({ description: 'Result of find and count' })
|
|
@@ -21,4 +22,11 @@ export class FindAndCountUsersResult {
|
|
|
21
22
|
isOptional: false,
|
|
22
23
|
})
|
|
23
24
|
totalCount: number;
|
|
25
|
+
|
|
26
|
+
@UnifiedField({
|
|
27
|
+
description: 'Pagination information',
|
|
28
|
+
isOptional: true,
|
|
29
|
+
type: () => PaginationInfo,
|
|
30
|
+
})
|
|
31
|
+
pagination?: PaginationInfo;
|
|
24
32
|
}
|