@lenne.tech/nest-server 11.21.3 → 11.22.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.
Files changed (52) hide show
  1. package/.claude/rules/architecture.md +79 -0
  2. package/.claude/rules/better-auth.md +262 -0
  3. package/.claude/rules/configurable-features.md +308 -0
  4. package/.claude/rules/core-modules.md +205 -0
  5. package/.claude/rules/migration-guides.md +149 -0
  6. package/.claude/rules/module-deprecation.md +214 -0
  7. package/.claude/rules/module-inheritance.md +97 -0
  8. package/.claude/rules/package-management.md +112 -0
  9. package/.claude/rules/role-system.md +146 -0
  10. package/.claude/rules/testing.md +120 -0
  11. package/.claude/rules/versioning.md +53 -0
  12. package/CLAUDE.md +172 -0
  13. package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
  14. package/dist/core/modules/error-code/error-code.module.js.map +1 -1
  15. package/dist/core.module.d.ts +3 -3
  16. package/dist/core.module.js +17 -4
  17. package/dist/core.module.js.map +1 -1
  18. package/dist/server/server.module.js +6 -6
  19. package/dist/server/server.module.js.map +1 -1
  20. package/dist/tsconfig.build.tsbuildinfo +1 -1
  21. package/docs/REQUEST-LIFECYCLE.md +1256 -0
  22. package/docs/error-codes.md +446 -0
  23. package/migration-guides/11.10.x-to-11.11.x.md +266 -0
  24. package/migration-guides/11.11.x-to-11.12.x.md +323 -0
  25. package/migration-guides/11.12.x-to-11.13.0.md +612 -0
  26. package/migration-guides/11.13.x-to-11.14.0.md +348 -0
  27. package/migration-guides/11.14.x-to-11.15.0.md +262 -0
  28. package/migration-guides/11.15.0-to-11.15.3.md +118 -0
  29. package/migration-guides/11.15.x-to-11.16.0.md +497 -0
  30. package/migration-guides/11.16.x-to-11.17.0.md +130 -0
  31. package/migration-guides/11.17.x-to-11.18.0.md +393 -0
  32. package/migration-guides/11.18.x-to-11.19.0.md +151 -0
  33. package/migration-guides/11.19.x-to-11.20.0.md +170 -0
  34. package/migration-guides/11.20.x-to-11.21.0.md +216 -0
  35. package/migration-guides/11.21.0-to-11.21.1.md +194 -0
  36. package/migration-guides/11.21.1-to-11.21.2.md +114 -0
  37. package/migration-guides/11.21.2-to-11.21.3.md +175 -0
  38. package/migration-guides/11.21.x-to-11.22.0.md +224 -0
  39. package/migration-guides/11.3.x-to-11.4.x.md +233 -0
  40. package/migration-guides/11.6.x-to-11.7.x.md +394 -0
  41. package/migration-guides/11.7.x-to-11.8.x.md +318 -0
  42. package/migration-guides/11.8.x-to-11.9.x.md +322 -0
  43. package/migration-guides/11.9.x-to-11.10.x.md +571 -0
  44. package/migration-guides/TEMPLATE.md +113 -0
  45. package/package.json +8 -3
  46. package/src/core/common/interfaces/server-options.interface.ts +83 -16
  47. package/src/core/modules/better-auth/CUSTOMIZATION.md +24 -17
  48. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +5 -5
  49. package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +42 -12
  50. package/src/core/modules/error-code/error-code.module.ts +4 -9
  51. package/src/core.module.ts +52 -10
  52. package/src/server/server.module.ts +7 -9
@@ -0,0 +1,446 @@
1
+ # Error Codes Documentation
2
+
3
+ This document lists all available error codes with their descriptions and solutions.
4
+
5
+ ## Error Code Format
6
+
7
+ All error codes follow the format: `#PREFIX_XXXX: Description`
8
+
9
+ - **PREFIX**: Identifies the source module (e.g., LTNS for core)
10
+ - **XXXX**: Unique 4-digit number within the prefix
11
+
12
+ ## Error Code Ranges
13
+
14
+ | Range | Category |
15
+ |-------|----------|
16
+ | LTNS_0001-LTNS_0099 | Authentication errors |
17
+ | LTNS_0100-LTNS_0199 | Authorization errors |
18
+ | LTNS_0200-LTNS_0299 | User errors |
19
+ | LTNS_0300-LTNS_0399 | Validation errors |
20
+ | LTNS_0400-LTNS_0499 | Resource errors |
21
+ | LTNS_0500-LTNS_0599 | File errors |
22
+ | LTNS_0600-LTNS_0699 | Database errors |
23
+ | LTNS_0700-LTNS_0799 | External service errors |
24
+ | LTNS_0800-LTNS_0899 | Configuration errors |
25
+ | LTNS_0900-LTNS_0999 | Internal errors |
26
+
27
+ ---
28
+
29
+ ## Authentication Errors (LTNS_0001-LTNS_0099)
30
+
31
+ ### LTNS_0001: userNotFound
32
+
33
+ **Message:** User not found with given email
34
+
35
+ **Description:** Thrown when email lookup fails during authentication
36
+
37
+ **Solution:** Verify the email address exists in the database
38
+
39
+ **Parameters:** `email`
40
+
41
+ **Translations:**
42
+ - DE: Benutzer mit E-Mail {email} wurde nicht gefunden.
43
+ - EN: User with email {email} not found.
44
+
45
+ ---
46
+
47
+ ### LTNS_0002: invalidPassword
48
+
49
+ **Message:** Invalid password provided
50
+
51
+ **Description:** Thrown when password verification fails during authentication
52
+
53
+ **Solution:** Verify the password meets requirements and matches the stored hash
54
+
55
+ **Translations:**
56
+ - DE: Das eingegebene Passwort ist ungültig.
57
+ - EN: The provided password is invalid.
58
+
59
+ ---
60
+
61
+ ### LTNS_0003: invalidToken
62
+
63
+ **Message:** Invalid or expired token
64
+
65
+ **Description:** Thrown when JWT validation fails
66
+
67
+ **Solution:** Request a new access token using refresh token or re-authenticate
68
+
69
+ **Translations:**
70
+ - DE: Der Token ist ungültig oder abgelaufen.
71
+ - EN: The token is invalid or has expired.
72
+
73
+ ---
74
+
75
+ ### LTNS_0004: tokenExpired
76
+
77
+ **Message:** Token has expired
78
+
79
+ **Description:** Thrown when JWT expiration time has passed
80
+
81
+ **Solution:** Use refresh token to get new access token or re-authenticate
82
+
83
+ **Translations:**
84
+ - DE: Der Token ist abgelaufen. Bitte melden Sie sich erneut an.
85
+ - EN: The token has expired. Please sign in again.
86
+
87
+ ---
88
+
89
+ ### LTNS_0005: refreshTokenRequired
90
+
91
+ **Message:** Refresh token is required
92
+
93
+ **Description:** Thrown when attempting to refresh without providing refresh token
94
+
95
+ **Solution:** Include the refresh token in the authorization header or cookie
96
+
97
+ **Translations:**
98
+ - DE: Ein Refresh-Token ist erforderlich.
99
+ - EN: A refresh token is required.
100
+
101
+ ---
102
+
103
+ ### LTNS_0006: userNotVerified
104
+
105
+ **Message:** User email is not verified
106
+
107
+ **Description:** Thrown when user attempts action requiring verified status
108
+
109
+ **Solution:** Complete the email verification process
110
+
111
+ **Translations:**
112
+ - DE: Die E-Mail-Adresse wurde noch nicht verifiziert.
113
+ - EN: The email address has not been verified yet.
114
+
115
+ ---
116
+
117
+ ## Authorization Errors (LTNS_0100-LTNS_0199)
118
+
119
+ ### LTNS_0100: unauthorized
120
+
121
+ **Message:** Unauthorized access
122
+
123
+ **Description:** Thrown when accessing protected resource without authentication
124
+
125
+ **Solution:** Sign in to access this resource
126
+
127
+ **Translations:**
128
+ - DE: Sie sind nicht angemeldet.
129
+ - EN: You are not authenticated.
130
+
131
+ ---
132
+
133
+ ### LTNS_0101: accessDenied
134
+
135
+ **Message:** Access denied - insufficient permissions
136
+
137
+ **Description:** Thrown when user does not have the required role
138
+
139
+ **Solution:** Contact an administrator to request the required permissions
140
+
141
+ **Parameters:** `requiredRole`
142
+
143
+ **Translations:**
144
+ - DE: Zugriff verweigert. Sie benötigen die Rolle {requiredRole}.
145
+ - EN: Access denied. Role {requiredRole} is required.
146
+
147
+ ---
148
+
149
+ ### LTNS_0102: resourceForbidden
150
+
151
+ **Message:** Access to this resource is forbidden
152
+
153
+ **Description:** Thrown when user cannot access a resource they do not own
154
+
155
+ **Solution:** Verify you are the owner or have been granted access
156
+
157
+ **Parameters:** `resourceId`
158
+
159
+ **Translations:**
160
+ - DE: Der Zugriff auf diese Ressource ({resourceId}) ist nicht gestattet.
161
+ - EN: Access to this resource ({resourceId}) is forbidden.
162
+
163
+ ---
164
+
165
+ ## User Errors (LTNS_0200-LTNS_0299)
166
+
167
+ ### LTNS_0200: emailAlreadyExists
168
+
169
+ **Message:** Email address already registered
170
+
171
+ **Description:** Thrown when attempting to register with an existing email
172
+
173
+ **Solution:** Use a different email address or recover the existing account
174
+
175
+ **Parameters:** `email`
176
+
177
+ **Translations:**
178
+ - DE: Die E-Mail-Adresse {email} ist bereits registriert.
179
+ - EN: The email address {email} is already registered.
180
+
181
+ ---
182
+
183
+ ### LTNS_0201: usernameAlreadyExists
184
+
185
+ **Message:** Username already taken
186
+
187
+ **Description:** Thrown when attempting to register with an existing username
188
+
189
+ **Solution:** Choose a different username
190
+
191
+ **Parameters:** `username`
192
+
193
+ **Translations:**
194
+ - DE: Der Benutzername {username} ist bereits vergeben.
195
+ - EN: The username {username} is already taken.
196
+
197
+ ---
198
+
199
+ ## Validation Errors (LTNS_0300-LTNS_0399)
200
+
201
+ ### LTNS_0300: validationFailed
202
+
203
+ **Message:** Validation failed
204
+
205
+ **Description:** Thrown when input does not meet validation requirements
206
+
207
+ **Solution:** Check the validation rules and provide valid input
208
+
209
+ **Parameters:** `field`
210
+
211
+ **Translations:**
212
+ - DE: Validierung fehlgeschlagen für Feld {field}.
213
+ - EN: Validation failed for field {field}.
214
+
215
+ ---
216
+
217
+ ### LTNS_0301: requiredFieldMissing
218
+
219
+ **Message:** Required field is missing
220
+
221
+ **Description:** Thrown when a required field is not included in the request
222
+
223
+ **Solution:** Include the required field in your request
224
+
225
+ **Parameters:** `field`
226
+
227
+ **Translations:**
228
+ - DE: Das Pflichtfeld {field} fehlt.
229
+ - EN: The required field {field} is missing.
230
+
231
+ ---
232
+
233
+ ### LTNS_0302: invalidFieldFormat
234
+
235
+ **Message:** Invalid format for field
236
+
237
+ **Description:** Thrown when field value does not match expected format
238
+
239
+ **Solution:** Check the expected format and provide a valid value
240
+
241
+ **Parameters:** `field`, `expectedFormat`
242
+
243
+ **Translations:**
244
+ - DE: Ungültiges Format für {field}. Erwartet: {expectedFormat}.
245
+ - EN: Invalid format for {field}. Expected: {expectedFormat}.
246
+
247
+ ---
248
+
249
+ ### LTNS_0303: nonWhitelistedProperties
250
+
251
+ **Message:** Non-whitelisted properties found
252
+
253
+ **Description:** Thrown when request body contains properties not decorated with `@UnifiedField`. Only active when `nonWhitelistedFields: 'error'` is configured.
254
+
255
+ **Solution:** Remove the non-whitelisted properties from the request, or decorate them with `@UnifiedField` in the input class. If using `'strip'` mode (default), these properties are silently removed instead of throwing an error.
256
+
257
+ **Parameters:** `properties`
258
+
259
+ **Translations:**
260
+ - DE: Die folgenden Eigenschaften sind nicht erlaubt: {{properties}}. Nur mit @UnifiedField dekorierte Eigenschaften werden akzeptiert.
261
+ - EN: The following properties are not allowed: {{properties}}. Only properties decorated with @UnifiedField are accepted.
262
+
263
+ ---
264
+
265
+ ## Resource Errors (LTNS_0400-LTNS_0499)
266
+
267
+ ### LTNS_0400: resourceNotFound
268
+
269
+ **Message:** Resource not found
270
+
271
+ **Description:** Thrown when the requested resource does not exist
272
+
273
+ **Solution:** Verify the resource ID is correct
274
+
275
+ **Parameters:** `resourceType`, `resourceId`
276
+
277
+ **Translations:**
278
+ - DE: {resourceType} mit ID {resourceId} wurde nicht gefunden.
279
+ - EN: {resourceType} with ID {resourceId} was not found.
280
+
281
+ ---
282
+
283
+ ### LTNS_0401: resourceAlreadyExists
284
+
285
+ **Message:** Resource already exists
286
+
287
+ **Description:** Thrown when attempting to create a resource that already exists
288
+
289
+ **Solution:** Use update operation or choose a different identifier
290
+
291
+ **Parameters:** `resourceType`, `identifier`
292
+
293
+ **Translations:**
294
+ - DE: {resourceType} mit Kennung {identifier} existiert bereits.
295
+ - EN: {resourceType} with identifier {identifier} already exists.
296
+
297
+ ---
298
+
299
+ ## File Errors (LTNS_0500-LTNS_0599)
300
+
301
+ ### LTNS_0500: fileNotFound
302
+
303
+ **Message:** File not found
304
+
305
+ **Description:** Thrown when the requested file does not exist
306
+
307
+ **Solution:** Verify the file ID is correct
308
+
309
+ **Parameters:** `fileId`
310
+
311
+ **Translations:**
312
+ - DE: Datei mit ID {fileId} wurde nicht gefunden.
313
+ - EN: File with ID {fileId} was not found.
314
+
315
+ ---
316
+
317
+ ### LTNS_0501: fileUploadFailed
318
+
319
+ **Message:** File upload failed
320
+
321
+ **Description:** Thrown when file upload process encounters an error
322
+
323
+ **Solution:** Check file size limits, allowed formats, and try again
324
+
325
+ **Parameters:** `reason`
326
+
327
+ **Translations:**
328
+ - DE: Datei-Upload fehlgeschlagen: {reason}.
329
+ - EN: File upload failed: {reason}.
330
+
331
+ ---
332
+
333
+ ### LTNS_0502: fileTypeNotAllowed
334
+
335
+ **Message:** File type not allowed
336
+
337
+ **Description:** Thrown when uploaded file type is not in the allowed list
338
+
339
+ **Solution:** Convert or upload a file with an allowed type
340
+
341
+ **Parameters:** `fileType`, `allowedTypes`
342
+
343
+ **Translations:**
344
+ - DE: Dateityp {fileType} ist nicht erlaubt. Erlaubt: {allowedTypes}.
345
+ - EN: File type {fileType} is not allowed. Allowed: {allowedTypes}.
346
+
347
+ ---
348
+
349
+ ## Internal Errors (LTNS_0900-LTNS_0999)
350
+
351
+ ### LTNS_0900: internalError
352
+
353
+ **Message:** An internal error occurred
354
+
355
+ **Description:** Thrown when an unexpected internal error occurs
356
+
357
+ **Solution:** Contact support if the issue persists
358
+
359
+ **Translations:**
360
+ - DE: Ein interner Fehler ist aufgetreten. Bitte versuchen Sie es später erneut.
361
+ - EN: An internal error occurred. Please try again later.
362
+
363
+ ---
364
+
365
+ ### LTNS_0901: serviceUnavailable
366
+
367
+ **Message:** Service temporarily unavailable
368
+
369
+ **Description:** Thrown when an external service is not responding
370
+
371
+ **Solution:** Try again later or contact support
372
+
373
+ **Parameters:** `serviceName`
374
+
375
+ **Translations:**
376
+ - DE: Der Dienst {serviceName} ist vorübergehend nicht verfügbar.
377
+ - EN: Service {serviceName} is temporarily unavailable.
378
+
379
+ ---
380
+
381
+ ### LTNS_0902: legacyAuthDisabled
382
+
383
+ **Message:** Legacy authentication is disabled
384
+
385
+ **Description:** Thrown when trying to use disabled legacy auth endpoints
386
+
387
+ **Solution:** Migrate to BetterAuth (IAM) endpoints for authentication
388
+
389
+ **Parameters:** `endpoint`
390
+
391
+ **Translations:**
392
+ - DE: Der Legacy-Authentifizierungs-Endpoint {endpoint} ist deaktiviert. Bitte verwenden Sie BetterAuth (IAM).
393
+ - EN: Legacy authentication endpoint {endpoint} is disabled. Please use BetterAuth (IAM).
394
+
395
+ ---
396
+
397
+ ## Extending Error Codes
398
+
399
+ Projects can extend the error code system by creating their own error registry:
400
+
401
+ ```typescript
402
+ import { defineErrors, createProjectErrors, CoreErrorCodeService } from '@lenne.tech/nest-server';
403
+
404
+ // Define project-specific errors
405
+ const PROJECT_ERRORS = defineErrors({
406
+ PROJ_0001: {
407
+ name: 'orderNotFound',
408
+ message: 'Order not found',
409
+ params: ['orderId'] as const,
410
+ translations: {
411
+ de: 'Bestellung {orderId} nicht gefunden.',
412
+ en: 'Order {orderId} not found.',
413
+ },
414
+ docs: {
415
+ description: 'Thrown when order lookup fails',
416
+ solution: 'Verify the order ID',
417
+ },
418
+ },
419
+ });
420
+
421
+ // Create factory functions
422
+ export const ProjectErrors = createProjectErrors(PROJECT_ERRORS);
423
+
424
+ // Extend the service to register project errors
425
+ @Injectable()
426
+ export class ErrorCodeService extends CoreErrorCodeService {
427
+ constructor() {
428
+ super();
429
+ this.registerErrors(PROJECT_ERRORS);
430
+ }
431
+ }
432
+ ```
433
+
434
+ ## Usage in Code
435
+
436
+ ```typescript
437
+ import { Errors } from '@lenne.tech/nest-server';
438
+
439
+ // Throw an error with parameters
440
+ throw Errors.userNotFound('user@example.com');
441
+ // → "#LTNS_0001: User not found with given email"
442
+
443
+ // Throw an error without parameters
444
+ throw Errors.invalidPassword();
445
+ // → "#LTNS_0002: Invalid password provided"
446
+ ```
@@ -0,0 +1,266 @@
1
+ # Migration Guide: 11.10.x → 11.11.x
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **New Features** | **PaginationInfo model** for paginated queries with navigation metadata |
9
+ | **Bugfixes** | None |
10
+ | **Migration Effort** | Very Low (~5 minutes) - No changes required, opt-in feature |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ ```bash
17
+ # Update package
18
+ npm install @lenne.tech/nest-server@11.11.x
19
+
20
+ # Verify build
21
+ npm run build
22
+
23
+ # Run tests
24
+ npm test
25
+ ```
26
+
27
+ **That's it!** This is a non-breaking update. The new `PaginationInfo` feature is opt-in.
28
+
29
+ ---
30
+
31
+ ## What's New in 11.11.x
32
+
33
+ ### 1. PaginationInfo Model for API Navigation
34
+
35
+ A new `PaginationInfo` model provides pagination metadata for paginated queries, making it easier to implement frontend pagination UI components.
36
+
37
+ **New Fields Available:**
38
+
39
+ | Field | Type | Description |
40
+ |-------|------|-------------|
41
+ | `totalCount` | number | Total number of items across all pages |
42
+ | `pageCount` | number | Total number of pages |
43
+ | `currentPage` | number | Current page number (1-based) |
44
+ | `perPage` | number | Number of items per page |
45
+ | `hasNextPage` | boolean | Indicates if there is a next page |
46
+ | `hasPreviousPage` | boolean | Indicates if there is a previous page |
47
+
48
+ **GraphQL Response Structure:**
49
+
50
+ The `findAndCount` method now returns an additional `pagination` field alongside the existing `items` and `totalCount`:
51
+
52
+ ```graphql
53
+ query {
54
+ findAndCountUsers(skip: 0, take: 10) {
55
+ items {
56
+ id
57
+ email
58
+ }
59
+ totalCount
60
+ pagination {
61
+ totalCount
62
+ pageCount
63
+ currentPage
64
+ perPage
65
+ hasNextPage
66
+ hasPreviousPage
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Example Response:**
73
+
74
+ ```json
75
+ {
76
+ "data": {
77
+ "findAndCountUsers": {
78
+ "items": [...],
79
+ "totalCount": 47,
80
+ "pagination": {
81
+ "totalCount": 47,
82
+ "pageCount": 5,
83
+ "currentPage": 1,
84
+ "perPage": 10,
85
+ "hasNextPage": true,
86
+ "hasPreviousPage": false
87
+ }
88
+ }
89
+ }
90
+ }
91
+ ```
92
+
93
+ ### 2. Using PaginationInfo in Custom Outputs
94
+
95
+ To use `PaginationInfo` in your own `findAndCount` result types, add the pagination field:
96
+
97
+ ```typescript
98
+ import { ObjectType } from '@nestjs/graphql';
99
+ import { PaginationInfo, UnifiedField } from '@lenne.tech/nest-server';
100
+ import { YourModel } from './your.model';
101
+
102
+ @ObjectType({ description: 'Result of find and count' })
103
+ export class FindAndCountYourModelResult {
104
+ @UnifiedField({
105
+ description: 'Found items',
106
+ isArray: true,
107
+ isOptional: true,
108
+ type: () => YourModel,
109
+ })
110
+ items: YourModel[];
111
+
112
+ @UnifiedField({
113
+ description: 'Total count (skip/offset and limit/take are ignored)',
114
+ isOptional: false,
115
+ })
116
+ totalCount: number;
117
+
118
+ // NEW: Add pagination field
119
+ @UnifiedField({
120
+ description: 'Pagination information',
121
+ isOptional: true,
122
+ type: () => PaginationInfo,
123
+ })
124
+ pagination?: PaginationInfo;
125
+ }
126
+ ```
127
+
128
+ ### 3. PaginationInfo.create() Static Method
129
+
130
+ The `PaginationInfo` model provides a static `create()` method for generating pagination metadata:
131
+
132
+ ```typescript
133
+ import { PaginationInfo } from '@lenne.tech/nest-server';
134
+
135
+ // Create pagination info from query parameters
136
+ const pagination = PaginationInfo.create({
137
+ skip: 20, // or offset
138
+ take: 10, // or limit
139
+ totalCount: 47,
140
+ });
141
+
142
+ // Result:
143
+ // {
144
+ // totalCount: 47,
145
+ // pageCount: 5,
146
+ // currentPage: 3,
147
+ // perPage: 10,
148
+ // hasNextPage: true,
149
+ // hasPreviousPage: true,
150
+ // }
151
+ ```
152
+
153
+ **Edge Cases:**
154
+
155
+ | Scenario | Result |
156
+ |----------|--------|
157
+ | Empty results (`totalCount: 0`) | `currentPage: 0`, `pageCount: 0`, `hasNextPage: false`, `hasPreviousPage: false` |
158
+ | No limit specified (`take: 0`) | All items treated as single page, `perPage: 0` |
159
+
160
+ ---
161
+
162
+ ## Breaking Changes
163
+
164
+ **None.** This is a fully backward-compatible update.
165
+
166
+ - Existing `findAndCount` queries continue to work without changes
167
+ - The `pagination` field is optional and only included when requested in the GraphQL query
168
+ - Existing `items` and `totalCount` fields remain unchanged
169
+
170
+ ---
171
+
172
+ ## Compatibility Notes
173
+
174
+ ### Existing findAndCount Queries
175
+
176
+ Queries that don't request the `pagination` field continue to work unchanged:
177
+
178
+ ```graphql
179
+ # Still works exactly as before
180
+ query {
181
+ findAndCountUsers(skip: 0, take: 10) {
182
+ items { id email }
183
+ totalCount
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### CrudService.findAndCount Return Type
189
+
190
+ The return type of `findAndCount` now includes `pagination`:
191
+
192
+ ```typescript
193
+ // Return type
194
+ Promise<{
195
+ items: Model[];
196
+ pagination: PaginationInfo;
197
+ totalCount: number;
198
+ }>
199
+ ```
200
+
201
+ This is backward compatible because:
202
+ - The `pagination` property is automatically calculated
203
+ - TypeScript will accept the extended return type
204
+ - GraphQL clients only receive fields they request
205
+
206
+ ---
207
+
208
+ ## Troubleshooting
209
+
210
+ ### Pagination Field Not Available in GraphQL
211
+
212
+ **Symptom:** The `pagination` field is not available when querying `findAndCount`.
213
+
214
+ **Solution:** Ensure your output type includes the `pagination` field:
215
+
216
+ ```typescript
217
+ @UnifiedField({
218
+ description: 'Pagination information',
219
+ isOptional: true,
220
+ type: () => PaginationInfo,
221
+ })
222
+ pagination?: PaginationInfo;
223
+ ```
224
+
225
+ ### currentPage Shows 0
226
+
227
+ **Symptom:** `currentPage` returns 0 even with items in the result.
228
+
229
+ **Cause:** This happens when `totalCount` is 0 (empty result set).
230
+
231
+ **Expected Behavior:** When there are no results, `currentPage` is 0 to indicate "no page".
232
+
233
+ ---
234
+
235
+ ## Module Documentation
236
+
237
+ ### CrudService
238
+
239
+ - **Location:** `src/core/common/services/crud.service.ts`
240
+ - **Key Method:** `findAndCount()` - Now returns `pagination` alongside `items` and `totalCount`
241
+
242
+ ### PaginationInfo Model
243
+
244
+ - **Location:** `src/core/common/models/pagination-info.model.ts`
245
+ - **Export:** `@lenne.tech/nest-server`
246
+ - **Reference Implementation:** `src/server/modules/user/outputs/find-and-count-users-result.output.ts`
247
+
248
+ ---
249
+
250
+ ## New Exports
251
+
252
+ The following is now exported from `@lenne.tech/nest-server`:
253
+
254
+ ```typescript
255
+ // PaginationInfo model (11.11.0)
256
+ export { PaginationInfo } from './core/common/models/pagination-info.model';
257
+ ```
258
+
259
+ ---
260
+
261
+ ## References
262
+
263
+ - [CrudService](../src/core/common/services/crud.service.ts) - Base service with `findAndCount` method
264
+ - [PaginationInfo Model](../src/core/common/models/pagination-info.model.ts) - Pagination metadata model
265
+ - [User Output Example](../src/server/modules/user/outputs/find-and-count-users-result.output.ts) - Reference implementation
266
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)