@lenne.tech/nest-server 11.8.0 → 11.9.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 (65) hide show
  1. package/dist/config.env.js +3 -0
  2. package/dist/config.env.js.map +1 -1
  3. package/dist/core/common/interfaces/server-options.interface.d.ts +13 -0
  4. package/dist/core/modules/auth/guards/roles.guard.js +4 -3
  5. package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
  6. package/dist/core/modules/auth/services/core-auth.service.js +5 -4
  7. package/dist/core/modules/auth/services/core-auth.service.js.map +1 -1
  8. package/dist/core/modules/error-code/core-error-code.controller.d.ts +7 -0
  9. package/dist/core/modules/error-code/core-error-code.controller.js +45 -0
  10. package/dist/core/modules/error-code/core-error-code.controller.js.map +1 -0
  11. package/dist/core/modules/error-code/core-error-code.service.d.ts +16 -0
  12. package/dist/core/modules/error-code/core-error-code.service.js +65 -0
  13. package/dist/core/modules/error-code/core-error-code.service.js.map +1 -0
  14. package/dist/core/modules/error-code/error-code.module.d.ts +7 -0
  15. package/dist/core/modules/error-code/error-code.module.js +64 -0
  16. package/dist/core/modules/error-code/error-code.module.js.map +1 -0
  17. package/dist/core/modules/error-code/error-codes.d.ts +219 -0
  18. package/dist/core/modules/error-code/error-codes.js +204 -0
  19. package/dist/core/modules/error-code/error-codes.js.map +1 -0
  20. package/dist/core/modules/error-code/index.d.ts +5 -0
  21. package/dist/core/modules/error-code/index.js +22 -0
  22. package/dist/core/modules/error-code/index.js.map +1 -0
  23. package/dist/core/modules/error-code/interfaces/error-code.interfaces.d.ts +12 -0
  24. package/dist/core/modules/error-code/interfaces/error-code.interfaces.js +3 -0
  25. package/dist/core/modules/error-code/interfaces/error-code.interfaces.js.map +1 -0
  26. package/dist/core.module.js +8 -0
  27. package/dist/core.module.js.map +1 -1
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.js +1 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/server/modules/error-code/error-code.controller.d.ts +8 -0
  32. package/dist/server/modules/error-code/error-code.controller.js +55 -0
  33. package/dist/server/modules/error-code/error-code.controller.js.map +1 -0
  34. package/dist/server/modules/error-code/error-code.service.d.ts +4 -0
  35. package/dist/server/modules/error-code/error-code.service.js +27 -0
  36. package/dist/server/modules/error-code/error-code.service.js.map +1 -0
  37. package/dist/server/modules/error-code/error-codes.d.ts +45 -0
  38. package/dist/server/modules/error-code/error-codes.js +24 -0
  39. package/dist/server/modules/error-code/error-codes.js.map +1 -0
  40. package/dist/server/modules/error-code/index.d.ts +3 -0
  41. package/dist/server/modules/error-code/index.js +20 -0
  42. package/dist/server/modules/error-code/index.js.map +1 -0
  43. package/dist/server/server.module.js +7 -0
  44. package/dist/server/server.module.js.map +1 -1
  45. package/dist/tsconfig.build.tsbuildinfo +1 -1
  46. package/package.json +2 -2
  47. package/src/config.env.ts +4 -0
  48. package/src/core/common/interfaces/server-options.interface.ts +89 -0
  49. package/src/core/modules/auth/guards/roles.guard.ts +5 -4
  50. package/src/core/modules/auth/services/core-auth.service.ts +5 -4
  51. package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +288 -0
  52. package/src/core/modules/error-code/core-error-code.controller.ts +54 -0
  53. package/src/core/modules/error-code/core-error-code.service.ts +135 -0
  54. package/src/core/modules/error-code/error-code.module.ts +119 -0
  55. package/src/core/modules/error-code/error-codes.ts +405 -0
  56. package/src/core/modules/error-code/index.ts +14 -0
  57. package/src/core/modules/error-code/interfaces/error-code.interfaces.ts +99 -0
  58. package/src/core.module.ts +16 -0
  59. package/src/index.ts +6 -0
  60. package/src/server/modules/error-code/README.md +131 -0
  61. package/src/server/modules/error-code/error-code.controller.ts +91 -0
  62. package/src/server/modules/error-code/error-code.service.ts +42 -0
  63. package/src/server/modules/error-code/error-codes.ts +65 -0
  64. package/src/server/modules/error-code/index.ts +8 -0
  65. package/src/server/server.module.ts +10 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.8.0",
3
+ "version": "11.9.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",
@@ -21,7 +21,7 @@
21
21
  "build:pack": "npm pack && echo 'use file:/ROOT_PATH_TO_TGZ_FILE to integrate the package'",
22
22
  "build:dev": "npm run build && yalc push --private",
23
23
  "docs": "npm run docs:ci && open http://127.0.0.1:8080/ && open ./public/index.html && compodoc -p tsconfig.json -s ",
24
- "docs:bootstrap": "node extras/update-spectaql-version.mjs && npx -y spectaql ./spectaql.yml",
24
+ "docs:bootstrap": "node extras/update-spectaql-version.mjs && node scripts/run-spectaql.mjs",
25
25
  "docs:ci": "ts-node ./scripts/init-server.ts && npm run docs:bootstrap && compodoc -p tsconfig.json",
26
26
  "format": "prettier --write 'src/**/*.ts'",
27
27
  "format:staged": "pretty-quick --staged",
package/src/config.env.ts CHANGED
@@ -262,6 +262,10 @@ const config: { [env: string]: IServerOptions } = {
262
262
  verificationLink: 'http://localhost:4200/user/verification',
263
263
  },
264
264
  env: 'local',
265
+ // Disable auto-registration to allow Server ErrorCodeModule with SRV_* codes
266
+ errorCode: {
267
+ autoRegister: false,
268
+ },
265
269
  execAfterInit: 'npm run docs:bootstrap',
266
270
  filter: {
267
271
  maxLimit: null,
@@ -589,6 +589,70 @@ export interface IBetterAuthUserField {
589
589
  type: BetterAuthFieldType;
590
590
  }
591
591
 
592
+ /**
593
+ * Interface for Error Code module configuration
594
+ *
595
+ * Controls how the ErrorCodeModule is registered and configured.
596
+ *
597
+ * @since 11.9.0
598
+ */
599
+ export interface IErrorCode {
600
+ /**
601
+ * Additional error registry to merge with core LTNS_* errors
602
+ *
603
+ * Use this to add project-specific error codes with a custom prefix.
604
+ *
605
+ * @example
606
+ * ```typescript
607
+ * const ProjectErrors = {
608
+ * ORDER_NOT_FOUND: {
609
+ * code: 'PROJ_0001',
610
+ * message: 'Order not found',
611
+ * translations: { de: 'Bestellung nicht gefunden.', en: 'Order not found.' }
612
+ * }
613
+ * } as const satisfies IErrorRegistry;
614
+ *
615
+ * errorCode: {
616
+ * additionalErrorRegistry: ProjectErrors,
617
+ * }
618
+ * ```
619
+ */
620
+ additionalErrorRegistry?: Record<
621
+ string,
622
+ {
623
+ code: string;
624
+ message: string;
625
+ translations: { [locale: string]: string; de: string; en: string };
626
+ }
627
+ >;
628
+
629
+ /**
630
+ * Automatically register the ErrorCodeModule in CoreModule
631
+ *
632
+ * Set to `false` to disable auto-registration and provide your own
633
+ * ErrorCodeModule with custom controller and/or service.
634
+ *
635
+ * @default true
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * // In config.env.ts - disable auto-registration
640
+ * errorCode: {
641
+ * autoRegister: false,
642
+ * }
643
+ *
644
+ * // In server.module.ts - import your custom module
645
+ * @Module({
646
+ * imports: [
647
+ * CoreModule.forRoot(...),
648
+ * ErrorCodeModule.forRoot(), // Your custom module
649
+ * ],
650
+ * })
651
+ * ```
652
+ */
653
+ autoRegister?: boolean;
654
+ }
655
+
592
656
  /**
593
657
  * Interface for JWT configuration (main and refresh)
594
658
  */
@@ -766,6 +830,31 @@ export interface IServerOptions {
766
830
  */
767
831
  env?: string;
768
832
 
833
+ /**
834
+ * Configuration for the error code module
835
+ *
836
+ * Controls how error codes and translations are handled.
837
+ *
838
+ * @since 11.9.0
839
+ *
840
+ * @example
841
+ * ```typescript
842
+ * // Default: auto-register with core errors only
843
+ * errorCode: undefined
844
+ *
845
+ * // Add project-specific error codes
846
+ * errorCode: {
847
+ * additionalErrorRegistry: ProjectErrors,
848
+ * }
849
+ *
850
+ * // Disable auto-registration to provide your own module
851
+ * errorCode: {
852
+ * autoRegister: false,
853
+ * }
854
+ * ```
855
+ */
856
+ errorCode?: IErrorCode;
857
+
769
858
  /**
770
859
  * Exec a command after server is initialized
771
860
  * e.g. 'npm run docs:bootstrap'
@@ -1,4 +1,4 @@
1
- import { ExecutionContext, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
1
+ import { ExecutionContext, ForbiddenException, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
2
2
  import { ModuleRef, Reflector } from '@nestjs/core';
3
3
  import { GqlExecutionContext } from '@nestjs/graphql';
4
4
  import { getConnectionToken } from '@nestjs/mongoose';
@@ -7,6 +7,7 @@ import { firstValueFrom, isObservable } from 'rxjs';
7
7
 
8
8
  import { RoleEnum } from '../../../common/enums/role.enum';
9
9
  import { BetterAuthService } from '../../better-auth/better-auth.service';
10
+ import { ErrorCode } from '../../error-code';
10
11
  import { AuthGuardStrategy } from '../auth-guard-strategy.enum';
11
12
  import { ExpiredTokenException } from '../exceptions/expired-token.exception';
12
13
  import { InvalidTokenException } from '../exceptions/invalid-token.exception';
@@ -331,7 +332,7 @@ export class RolesGuard extends AuthGuard(AuthGuardStrategy.JWT) {
331
332
 
332
333
  // Check if locked
333
334
  if (roles && roles.includes(RoleEnum.S_NO_ONE)) {
334
- throw new UnauthorizedException('No access');
335
+ throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
335
336
  }
336
337
 
337
338
  // Check roles
@@ -354,11 +355,11 @@ export class RolesGuard extends AuthGuard(AuthGuardStrategy.JWT) {
354
355
  if (info?.name === 'TokenExpiredError') {
355
356
  throw new ExpiredTokenException();
356
357
  }
357
- throw new UnauthorizedException('Unauthorized');
358
+ throw new UnauthorizedException(ErrorCode.UNAUTHORIZED);
358
359
  }
359
360
 
360
361
  // Requester is not authorized
361
- throw new UnauthorizedException('Missing role');
362
+ throw new ForbiddenException(ErrorCode.ACCESS_DENIED);
362
363
  }
363
364
 
364
365
  // Everything is ok
@@ -17,6 +17,7 @@ import { ServiceOptions } from '../../../common/interfaces/service-options.inter
17
17
  import { ConfigService } from '../../../common/services/config.service';
18
18
  import { BetterAuthUserMapper } from '../../better-auth/better-auth-user.mapper';
19
19
  import { BetterAuthService } from '../../better-auth/better-auth.service';
20
+ import { ErrorCode } from '../../error-code';
20
21
  import { CoreAuthModel } from '../core-auth.model';
21
22
  import { CoreAuthSignInInput } from '../inputs/core-auth-sign-in.input';
22
23
  import { CoreAuthSignUpInput } from '../inputs/core-auth-sign-up.input';
@@ -83,13 +84,13 @@ export class CoreAuthService {
83
84
  // Check authentication
84
85
  const user = serviceOptions.currentUser;
85
86
  if (!user || !tokenOrRefreshToken) {
86
- throw new UnauthorizedException('Invalid token');
87
+ throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
87
88
  }
88
89
 
89
90
  // Check authorization
90
91
  const deviceId = this.decodeJwt(tokenOrRefreshToken)?.deviceId;
91
92
  if (!deviceId || !user.refreshTokens[deviceId]) {
92
- throw new UnauthorizedException('Invalid token');
93
+ throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
93
94
  }
94
95
 
95
96
  // Logout from all devices
@@ -374,7 +375,7 @@ export class CoreAuthService {
374
375
  if (currentRefreshToken) {
375
376
  deviceId = this.decodeJwt(currentRefreshToken)?.deviceId;
376
377
  if (!deviceId || !user.refreshTokens?.[deviceId]) {
377
- throw new UnauthorizedException('Invalid token');
378
+ throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
378
379
  }
379
380
  if (!this.configService.getFastButReadOnly('jwt.refresh.renewal')) {
380
381
  // Return currentToken
@@ -398,7 +399,7 @@ export class CoreAuthService {
398
399
  // Set new token
399
400
  const payload = this.decodeJwt(newRefreshToken);
400
401
  if (!payload) {
401
- throw new UnauthorizedException('Invalid token');
402
+ throw new UnauthorizedException(ErrorCode.INVALID_TOKEN);
402
403
  }
403
404
  if (!deviceId) {
404
405
  deviceId = payload.deviceId;
@@ -0,0 +1,288 @@
1
+ # ErrorCode Integration Checklist
2
+
3
+ **For integrating custom error codes into projects using `@lenne.tech/nest-server`.**
4
+
5
+ > **Estimated time:** 5-10 minutes
6
+
7
+ ---
8
+
9
+ ## Choose Your Scenario
10
+
11
+ | Scenario | Use When | Configuration | Complexity |
12
+ |----------|----------|---------------|------------|
13
+ | **A. additionalErrorRegistry** | Simple error code addition | Config in `config.env.ts` | Minimal |
14
+ | **B. Custom Service** | Need custom locales or logic | Service inheritance | Low |
15
+ | **C. Custom Controller** | Need custom controller/routes | Service + Controller | Medium |
16
+
17
+ **Recommendation:** Start with **Scenario A**. Only use B or C if you need customization beyond adding error codes.
18
+
19
+ ---
20
+
21
+ ## Reference Implementation
22
+
23
+ All files are available as reference in the package:
24
+
25
+ **Local (in your node_modules):**
26
+ ```
27
+ node_modules/@lenne.tech/nest-server/src/server/modules/error-code/
28
+ ```
29
+
30
+ **GitHub:**
31
+ https://github.com/lenneTech/nest-server/tree/develop/src/server/modules/error-code
32
+
33
+ ---
34
+
35
+ ## Scenario A: additionalErrorRegistry (Simplest)
36
+
37
+ Use this when you just want to add project-specific error codes.
38
+
39
+ ### 1. Define Error Codes
40
+
41
+ **Create:** `src/server/common/errors/project-errors.ts`
42
+
43
+ ```typescript
44
+ import { IErrorRegistry, mergeErrorCodes } from '@lenne.tech/nest-server';
45
+
46
+ /**
47
+ * Project-specific error codes
48
+ *
49
+ * Format: PREFIX_XXXX (e.g., PROJ_0001, APP_0001)
50
+ * Use a unique prefix to avoid collisions with LTNS_* core errors.
51
+ */
52
+ export const ProjectErrors = {
53
+ ORDER_NOT_FOUND: {
54
+ code: 'PROJ_0001',
55
+ message: 'Order not found',
56
+ translations: {
57
+ de: 'Bestellung mit ID {orderId} wurde nicht gefunden.',
58
+ en: 'Order with ID {orderId} was not found.',
59
+ },
60
+ },
61
+ PAYMENT_FAILED: {
62
+ code: 'PROJ_0002',
63
+ message: 'Payment processing failed',
64
+ translations: {
65
+ de: 'Die Zahlung konnte nicht verarbeitet werden: {reason}',
66
+ en: 'Payment processing failed: {reason}',
67
+ },
68
+ },
69
+ } as const satisfies IErrorRegistry;
70
+
71
+ // Merged error codes for type-safe factory functions
72
+ export const ErrorCode = mergeErrorCodes(ProjectErrors);
73
+ ```
74
+
75
+ ### 2. Add to config.env.ts
76
+
77
+ ```typescript
78
+ import { ProjectErrors } from './server/common/errors/project-errors';
79
+
80
+ const config = {
81
+ // ... other config ...
82
+ errorCode: {
83
+ additionalErrorRegistry: ProjectErrors,
84
+ },
85
+ };
86
+ ```
87
+
88
+ **Done!** Your project errors are now available via `/api/i18n/errors/:locale`.
89
+
90
+ ---
91
+
92
+ ## Scenario B: Custom Service (For Custom Locales)
93
+
94
+ Use this when you need:
95
+ - Additional locales (e.g., French, Spanish)
96
+ - Custom logic in the service
97
+
98
+ ### 1. Define Error Codes
99
+
100
+ Same as Scenario A, Step 1.
101
+
102
+ ### 2. Create Custom Service
103
+
104
+ **Create:** `src/server/modules/error-code/error-code.service.ts`
105
+ **Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/error-code/error-code.service.ts`
106
+
107
+ **Optional customization - add locales:**
108
+ ```typescript
109
+ @Injectable()
110
+ export class ErrorCodeService extends CoreErrorCodeService {
111
+ // Override to add more locales
112
+ protected override supportedLocales = ['de', 'en', 'fr', 'es'] as const;
113
+
114
+ constructor() {
115
+ super();
116
+ this.registerErrorRegistry(ProjectErrors);
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### 3. Disable Auto-Registration
122
+
123
+ **Update:** `src/config.env.ts`
124
+
125
+ ```typescript
126
+ const config = {
127
+ // ... other config ...
128
+ errorCode: {
129
+ autoRegister: false, // Required! Prevents CoreModule from registering its own
130
+ },
131
+ };
132
+ ```
133
+
134
+ **WHY is `autoRegister: false` required?**
135
+ NestJS @Global() modules use "first wins" for provider registration. Without this, CoreModule's ErrorCodeModule loads first and your custom service is ignored.
136
+
137
+ ### 4. Register in ServerModule
138
+
139
+ **Update:** `src/server/server.module.ts`
140
+
141
+ ```typescript
142
+ import { ErrorCodeModule as CoreErrorCodeModule } from '@lenne.tech/nest-server';
143
+ import { ErrorCodeService } from './modules/error-code/error-code.service';
144
+
145
+ @Module({
146
+ imports: [
147
+ CoreModule.forRoot(...),
148
+ // Register with custom service
149
+ CoreErrorCodeModule.forRoot({
150
+ service: ErrorCodeService,
151
+ }),
152
+ // ... other modules
153
+ ],
154
+ })
155
+ export class ServerModule {}
156
+ ```
157
+
158
+ ---
159
+
160
+ ## Scenario C: Custom Controller (For Custom Routes)
161
+
162
+ Use this when you need:
163
+ - Custom controller endpoints (e.g., `/codes` listing)
164
+ - Different route paths
165
+ - Additional REST endpoints
166
+
167
+ **No custom module needed!** Use Core `ErrorCodeModule.forRoot()` with your custom controller and service.
168
+
169
+ ### 1. Create Files
170
+
171
+ **Copy from:** `node_modules/@lenne.tech/nest-server/src/server/modules/error-code/`
172
+
173
+ Files needed:
174
+ - `error-codes.ts` - Your error definitions
175
+ - `error-code.service.ts` - Service extending CoreErrorCodeService
176
+ - `error-code.controller.ts` - Controller (**standalone**, not extending)
177
+ - `index.ts` - Exports
178
+
179
+ **No `error-code.module.ts` needed!**
180
+
181
+ ### 2. Disable Auto-Registration
182
+
183
+ Same as Scenario B, Step 3.
184
+
185
+ ### 3. Register via Core ErrorCodeModule
186
+
187
+ **Update:** `src/server/server.module.ts`
188
+
189
+ ```typescript
190
+ import { ErrorCodeModule } from '@lenne.tech/nest-server';
191
+ import { ErrorCodeController } from './modules/error-code/error-code.controller';
192
+ import { ErrorCodeService } from './modules/error-code/error-code.service';
193
+
194
+ @Module({
195
+ imports: [
196
+ CoreModule.forRoot(...),
197
+ // Use Core ErrorCodeModule with custom service and controller
198
+ ErrorCodeModule.forRoot({
199
+ controller: ErrorCodeController,
200
+ service: ErrorCodeService,
201
+ }),
202
+ // ... other modules
203
+ ],
204
+ })
205
+ export class ServerModule {}
206
+ ```
207
+
208
+ **WHY standalone controller instead of extending?**
209
+ NestJS registers routes from parent classes first, regardless of method order in child classes. This causes `:locale` to intercept `/codes`. A standalone controller ensures correct route order: static routes (`/codes`) first, then parameterized routes (`:locale`).
210
+
211
+ ---
212
+
213
+ ## Verification Checklist
214
+
215
+ After integration, verify:
216
+
217
+ - [ ] `npm run build` succeeds without errors
218
+ - [ ] `npm test` passes
219
+ - [ ] `GET /api/i18n/errors/de` returns your project error codes
220
+ - [ ] `GET /api/i18n/errors/en` returns English translations
221
+ - [ ] Error codes follow format `PREFIX_XXXX` (e.g., `PROJ_0001`)
222
+ - [ ] Translations include placeholders where needed (`{param}`)
223
+
224
+ ### For Scenario C only:
225
+ - [ ] `GET /api/i18n/errors/codes` returns all error codes (if implemented)
226
+
227
+ ---
228
+
229
+ ## Common Mistakes
230
+
231
+ | Mistake | Symptom | Fix |
232
+ |---------|---------|-----|
233
+ | Forgot `autoRegister: false` | Project errors not appearing | Add `errorCode: { autoRegister: false }` to config |
234
+ | Wrong error code format | Validation errors | Use `PREFIX_XXXX` format (4 digits) |
235
+ | Missing translations | Runtime errors | Ensure all locales have translations |
236
+ | Controller extends CoreErrorCodeController | `/codes` returns 404 | Use standalone controller |
237
+ | Duplicate error codes | Unpredictable behavior | Ensure unique codes across all registries |
238
+ | Forgot to import module | No error translations | Import ErrorCodeModule in ServerModule |
239
+
240
+ ---
241
+
242
+ ## Using Error Codes in Code
243
+
244
+ ```typescript
245
+ import { ErrorCode, Errors } from '@lenne.tech/nest-server';
246
+
247
+ // Type-safe error code access
248
+ const code = ErrorCode.userNotFound; // Returns '#LTNS_0001: User not found'
249
+
250
+ // Factory functions with parameters
251
+ throw new BadRequestException(Errors.userNotFound({ email: 'test@example.com' }));
252
+ // Throws: '#LTNS_0001: User with email test@example.com was not found.'
253
+
254
+ // Project-specific errors (after registration)
255
+ import { ErrorCode as ProjectErrorCode } from './common/errors/project-errors';
256
+
257
+ const orderCode = ProjectErrorCode.ORDER_NOT_FOUND; // '#PROJ_0001: Order not found'
258
+ ```
259
+
260
+ ---
261
+
262
+ ## API Reference
263
+
264
+ ### REST Endpoints
265
+
266
+ | Endpoint | Method | Description |
267
+ |----------|--------|-------------|
268
+ | `/api/i18n/errors/:locale` | GET | Get translations for locale (de, en, ...) |
269
+ | `/api/i18n/errors/codes` | GET | Get all error codes (Scenario C only) |
270
+
271
+ ### Response Format (Nuxt i18n compatible)
272
+
273
+ ```json
274
+ {
275
+ "errors": {
276
+ "LTNS_0001": "Benutzer mit E-Mail {email} wurde nicht gefunden.",
277
+ "PROJ_0001": "Bestellung mit ID {orderId} wurde nicht gefunden."
278
+ }
279
+ }
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Detailed Documentation
285
+
286
+ For complete API reference and advanced topics:
287
+ - **Core Error Codes:** `node_modules/@lenne.tech/nest-server/src/core/modules/error-code/error-codes.ts`
288
+ - **Interfaces:** `node_modules/@lenne.tech/nest-server/src/core/modules/error-code/interfaces/error-code.interfaces.ts`
@@ -0,0 +1,54 @@
1
+ import { Controller, Get, NotFoundException, Param } from '@nestjs/common';
2
+
3
+ import { Roles } from '../../common/decorators/roles.decorator';
4
+ import { RoleEnum } from '../../common/enums/role.enum';
5
+ import { CoreErrorCodeService } from './core-error-code.service';
6
+ import { IErrorTranslationResponse, SupportedLocale } from './interfaces/error-code.interfaces';
7
+
8
+ /**
9
+ * Core Error Code Controller
10
+ *
11
+ * Provides REST endpoints for error translations.
12
+ * This controller is publicly accessible (no authentication required).
13
+ *
14
+ * @example
15
+ * GET /api/i18n/errors/de - Get German translations
16
+ * GET /api/i18n/errors/en - Get English translations
17
+ */
18
+ @Controller('api/i18n/errors')
19
+ export class CoreErrorCodeController {
20
+ constructor(protected readonly errorCodeService: CoreErrorCodeService) {}
21
+
22
+ /**
23
+ * Get error translations for a specific locale
24
+ *
25
+ * Returns all error codes with their translations in Nuxt i18n compatible format.
26
+ *
27
+ * @param locale - Locale code (e.g., 'de', 'en')
28
+ * @returns Translations object
29
+ * @throws NotFoundException if locale is not supported
30
+ *
31
+ * @example
32
+ * Response:
33
+ * ```json
34
+ * {
35
+ * "errors": {
36
+ * "LTNS_0001": "Benutzer mit E-Mail {email} wurde nicht gefunden.",
37
+ * "LTNS_0002": "Das eingegebene Passwort ist ungültig."
38
+ * }
39
+ * }
40
+ * ```
41
+ */
42
+ @Get(':locale')
43
+ @Roles(RoleEnum.S_EVERYONE)
44
+ getTranslations(@Param('locale') locale: string): IErrorTranslationResponse {
45
+ if (!this.errorCodeService.isLocaleSupported(locale)) {
46
+ throw new NotFoundException(
47
+ `Locale "${locale}" is not supported. ` +
48
+ `Supported locales: ${this.errorCodeService.getSupportedLocales().join(', ')}`,
49
+ );
50
+ }
51
+
52
+ return this.errorCodeService.getTranslations(locale as SupportedLocale);
53
+ }
54
+ }
@@ -0,0 +1,135 @@
1
+ import { Injectable } from '@nestjs/common';
2
+
3
+ import { getAllErrorDefinitions, IErrorRegistry } from './error-codes';
4
+ import { SupportedLocale } from './interfaces/error-code.interfaces';
5
+
6
+ /**
7
+ * Core Error Code Service
8
+ *
9
+ * Serves error code translations from the structured ErrorRegistry.
10
+ * Translations are defined in error-codes.ts as Single Source of Truth.
11
+ *
12
+ * Projects can extend this service to add custom error registries.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // In consuming project:
17
+ * import { CoreErrorCodeService, IErrorRegistry } from '@lenne.tech/nest-server';
18
+ *
19
+ * const ProjectErrors = {
20
+ * ORDER_NOT_FOUND: {
21
+ * code: 'PROJ_0001',
22
+ * message: 'Order not found',
23
+ * translations: { de: 'Bestellung nicht gefunden.', en: 'Order not found.' }
24
+ * }
25
+ * } as const satisfies IErrorRegistry;
26
+ *
27
+ * @Injectable()
28
+ * export class ErrorCodeService extends CoreErrorCodeService {
29
+ * constructor() {
30
+ * super();
31
+ * this.registerErrorRegistry(ProjectErrors);
32
+ * }
33
+ * }
34
+ * ```
35
+ */
36
+ @Injectable()
37
+ export class CoreErrorCodeService {
38
+ /**
39
+ * Supported locales
40
+ */
41
+ protected supportedLocales: SupportedLocale[] = ['de', 'en'];
42
+
43
+ /**
44
+ * Cached translations per locale
45
+ */
46
+ protected translations: Map<SupportedLocale, Record<string, string>> = new Map();
47
+
48
+ /**
49
+ * Registered error registries
50
+ */
51
+ protected registries: IErrorRegistry[] = [];
52
+
53
+ constructor() {
54
+ // Initialize with core errors
55
+ this.registerErrorRegistry(getAllErrorDefinitions());
56
+ }
57
+
58
+ /**
59
+ * Register an error registry and generate translations
60
+ *
61
+ * @param registry - Error registry to register
62
+ */
63
+ registerErrorRegistry(registry: IErrorRegistry): void {
64
+ this.registries.push(registry);
65
+ this.generateTranslationsFromRegistry(registry);
66
+ }
67
+
68
+ /**
69
+ * Generate translations from error registry
70
+ *
71
+ * @param registry - Error registry to extract translations from
72
+ */
73
+ protected generateTranslationsFromRegistry(registry: IErrorRegistry): void {
74
+ for (const [, definition] of Object.entries(registry)) {
75
+ const { code, translations: defTranslations } = definition;
76
+
77
+ for (const locale of this.supportedLocales) {
78
+ const translation = defTranslations[locale];
79
+ if (translation) {
80
+ const existing = this.translations.get(locale) || {};
81
+ this.translations.set(locale, { ...existing, [code]: translation });
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Check if a locale is supported
89
+ *
90
+ * @param locale - Locale to check
91
+ * @returns True if locale is supported
92
+ */
93
+ isLocaleSupported(locale: string): locale is SupportedLocale {
94
+ return this.supportedLocales.includes(locale as SupportedLocale);
95
+ }
96
+
97
+ /**
98
+ * Get supported locales
99
+ *
100
+ * @returns Array of supported locales
101
+ */
102
+ getSupportedLocales(): SupportedLocale[] {
103
+ return [...this.supportedLocales];
104
+ }
105
+
106
+ /**
107
+ * Get all translations for a locale
108
+ *
109
+ * @param locale - Locale code (e.g., 'de', 'en')
110
+ * @returns Translations object wrapped in { errors: ... } for Nuxt i18n compatibility
111
+ * @throws Error if locale is not supported
112
+ */
113
+ getTranslations(locale: SupportedLocale): { errors: Record<string, string> } {
114
+ if (!this.isLocaleSupported(locale)) {
115
+ throw new Error(`Locale "${locale}" is not supported. Supported: ${this.supportedLocales.join(', ')}`);
116
+ }
117
+
118
+ return { errors: this.translations.get(locale) || {} };
119
+ }
120
+
121
+ /**
122
+ * Get all error codes
123
+ *
124
+ * @returns Array of error codes from all registries
125
+ */
126
+ getErrorCodes(): string[] {
127
+ const codes = new Set<string>();
128
+ for (const translations of this.translations.values()) {
129
+ for (const code of Object.keys(translations)) {
130
+ codes.add(code);
131
+ }
132
+ }
133
+ return Array.from(codes).sort();
134
+ }
135
+ }