@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.10.5",
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.2.0",
81
+ "@apollo/server": "5.3.0",
82
82
  "@as-integrations/express5": "1.1.2",
83
- "@better-auth/passkey": "1.4.16",
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.9",
87
- "@nestjs/core": "11.1.9",
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.9",
92
+ "@nestjs/platform-express": "11.1.12",
93
93
  "@nestjs/schedule": "6.1.0",
94
- "@nestjs/swagger": "11.2.3",
94
+ "@nestjs/swagger": "11.2.5",
95
95
  "@nestjs/terminus": "11.0.0",
96
- "@nestjs/websockets": "11.1.9",
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.16",
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.21",
115
+ "lodash": "4.17.23",
116
116
  "mongodb": "7.0.0",
117
- "mongoose": "9.0.2",
117
+ "mongoose": "9.1.5",
118
118
  "multer": "2.0.2",
119
119
  "node-mailjet": "6.0.11",
120
- "nodemailer": "7.0.11",
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.32",
129
+ "@compodoc/compodoc": "1.2.1",
130
130
  "@lenne.tech/eslint-config-ts": "2.1.4",
131
- "@nestjs/cli": "11.0.14",
131
+ "@nestjs/cli": "11.0.16",
132
132
  "@nestjs/schematics": "11.0.9",
133
- "@nestjs/testing": "11.1.9",
133
+ "@nestjs/testing": "11.1.12",
134
134
  "@swc/cli": "0.7.9",
135
- "@swc/core": "1.15.7",
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.21",
140
+ "@types/lodash": "4.17.23",
141
141
  "@types/multer": "2.0.0",
142
- "@types/node": "25.0.3",
143
- "@types/nodemailer": "7.0.4",
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.50.0",
147
- "@typescript-eslint/parser": "8.50.0",
148
- "@vitest/coverage-v8": "4.0.16",
149
- "@vitest/ui": "4.0.16",
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.1.4",
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.0",
177
+ "vite": "7.3.1",
178
178
  "vite-plugin-node": "7.0.0",
179
- "vite-tsconfig-paths": "6.0.3",
180
- "vitest": "4.0.16",
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<{ items: Model[]; totalCount: number }> {
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
  }