@lenne.tech/nest-server 11.21.3 → 11.22.1

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 (57) 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/framework-compatibility.md +79 -0
  6. package/.claude/rules/migration-guides.md +149 -0
  7. package/.claude/rules/module-deprecation.md +214 -0
  8. package/.claude/rules/module-inheritance.md +97 -0
  9. package/.claude/rules/package-management.md +112 -0
  10. package/.claude/rules/role-system.md +146 -0
  11. package/.claude/rules/testing.md +120 -0
  12. package/.claude/rules/versioning.md +53 -0
  13. package/CLAUDE.md +174 -0
  14. package/FRAMEWORK-API.md +231 -0
  15. package/dist/core/common/interfaces/server-options.interface.d.ts +10 -0
  16. package/dist/core/modules/error-code/error-code.module.js.map +1 -1
  17. package/dist/core.module.d.ts +3 -3
  18. package/dist/core.module.js +17 -4
  19. package/dist/core.module.js.map +1 -1
  20. package/dist/server/modules/file/file-info.model.d.ts +1 -5
  21. package/dist/server/modules/user/user.model.d.ts +1 -5
  22. package/dist/server/server.module.js +6 -6
  23. package/dist/server/server.module.js.map +1 -1
  24. package/dist/tsconfig.build.tsbuildinfo +1 -1
  25. package/docs/REQUEST-LIFECYCLE.md +1256 -0
  26. package/docs/error-codes.md +446 -0
  27. package/migration-guides/11.10.x-to-11.11.x.md +266 -0
  28. package/migration-guides/11.11.x-to-11.12.x.md +323 -0
  29. package/migration-guides/11.12.x-to-11.13.0.md +612 -0
  30. package/migration-guides/11.13.x-to-11.14.0.md +348 -0
  31. package/migration-guides/11.14.x-to-11.15.0.md +262 -0
  32. package/migration-guides/11.15.0-to-11.15.3.md +118 -0
  33. package/migration-guides/11.15.x-to-11.16.0.md +497 -0
  34. package/migration-guides/11.16.x-to-11.17.0.md +130 -0
  35. package/migration-guides/11.17.x-to-11.18.0.md +393 -0
  36. package/migration-guides/11.18.x-to-11.19.0.md +151 -0
  37. package/migration-guides/11.19.x-to-11.20.0.md +170 -0
  38. package/migration-guides/11.20.x-to-11.21.0.md +216 -0
  39. package/migration-guides/11.21.0-to-11.21.1.md +194 -0
  40. package/migration-guides/11.21.1-to-11.21.2.md +114 -0
  41. package/migration-guides/11.21.2-to-11.21.3.md +175 -0
  42. package/migration-guides/11.21.x-to-11.22.0.md +224 -0
  43. package/migration-guides/11.22.0-to-11.22.1.md +105 -0
  44. package/migration-guides/11.3.x-to-11.4.x.md +233 -0
  45. package/migration-guides/11.6.x-to-11.7.x.md +394 -0
  46. package/migration-guides/11.7.x-to-11.8.x.md +318 -0
  47. package/migration-guides/11.8.x-to-11.9.x.md +322 -0
  48. package/migration-guides/11.9.x-to-11.10.x.md +571 -0
  49. package/migration-guides/TEMPLATE.md +113 -0
  50. package/package.json +25 -18
  51. package/src/core/common/interfaces/server-options.interface.ts +83 -16
  52. package/src/core/modules/better-auth/CUSTOMIZATION.md +24 -17
  53. package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +5 -5
  54. package/src/core/modules/error-code/INTEGRATION-CHECKLIST.md +42 -12
  55. package/src/core/modules/error-code/error-code.module.ts +4 -9
  56. package/src/core.module.ts +52 -10
  57. package/src/server/server.module.ts +7 -9
@@ -0,0 +1,118 @@
1
+ # Migration Guide: 11.15.0 → 11.15.3
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **New Features** | Cross-subdomain cookie Boolean Shorthand, middleware domain fix |
9
+ | **Bugfixes** | Fixed middleware not setting cookie domain, fixed `auto-install-peers` in `.npmrc` |
10
+ | **Migration Effort** | None required — update package only. New feature is opt-in. |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ ```bash
17
+ # Update package
18
+ pnpm install @lenne.tech/nest-server@11.15.3
19
+
20
+ # Verify build
21
+ pnpm run build
22
+
23
+ # Run tests
24
+ pnpm test
25
+ ```
26
+
27
+ No code changes required. All changes are backward compatible.
28
+
29
+ ---
30
+
31
+ ## What's New in 11.15.3
32
+
33
+ ### 1. Cross-Subdomain Cookie Boolean Shorthand
34
+
35
+ When your API and other services run on different subdomains (e.g., `api.example.com` and `ws.example.com`), authentication cookies are not shared by default. This feature provides a simple Boolean Shorthand to enable cross-subdomain cookie sharing.
36
+
37
+ **New recommended configuration:**
38
+
39
+ ```typescript
40
+ // config.env.ts
41
+ const config = {
42
+ baseUrl: 'https://api.dev.example.com',
43
+ appUrl: 'https://dev.example.com',
44
+ betterAuth: {
45
+ crossSubDomainCookies: true, // Domain auto-derived → 'dev.example.com'
46
+ },
47
+ };
48
+ ```
49
+
50
+ **Domain resolution order:** `appUrl` hostname → `baseUrl` hostname (with `api.` prefix stripped) → `baseUrl` hostname as-is.
51
+
52
+ **Replaces the verbose `options.advanced` passthrough:**
53
+
54
+ ```typescript
55
+ // OLD (still works, but not recommended)
56
+ betterAuth: {
57
+ options: {
58
+ advanced: {
59
+ crossSubDomainCookies: { domain: 'example.com' },
60
+ },
61
+ },
62
+ }
63
+
64
+ // NEW (recommended)
65
+ betterAuth: {
66
+ crossSubDomainCookies: true, // or { domain: 'example.com' }
67
+ }
68
+ ```
69
+
70
+ **Boolean Shorthand options:**
71
+
72
+ | Value | Behavior |
73
+ |-------|----------|
74
+ | `undefined` | Disabled (default, backward compatible) |
75
+ | `true` or `{}` | Enabled, domain auto-derived from `baseUrl` hostname |
76
+ | `{ domain: 'example.com' }` | Enabled with explicit domain |
77
+ | `false` or `{ enabled: false }` | Disabled |
78
+
79
+ **What this does:**
80
+ - Sets the `domain` attribute on all authentication cookies (`iam.session_token`, and `token` if Legacy Auth is active)
81
+ - Enables cookie sharing between subdomains (e.g., `api.example.com` ↔ `ws.example.com`)
82
+ - Configures both Better-Auth's native handler AND nest-server's own cookie helper (controller + middleware)
83
+ - Automatically disabled for localhost (not meaningful for cross-subdomain)
84
+
85
+ **Important:** `crossSubDomainCookies` handles only the **cookie domain** (browser sends cookies to subdomains). Better-Auth also validates request origins via `trustedOrigins` (server accepts requests from subdomains). Both are required. `trustedOrigins` is auto-derived from `baseUrl`/`appUrl`, but additional subdomains (e.g., `https://ws.example.com`) must be added explicitly via `trustedOrigins`.
86
+
87
+ **Documentation:** [Better-Auth README — Cross-Subdomain Cookies](../src/core/modules/better-auth/README.md#cross-subdomain-cookies-v11151)
88
+
89
+ ### 2. Middleware Cookie Domain Fix
90
+
91
+ **Bug:** The API middleware (`CoreBetterAuthApiMiddleware`) did not read the cookie domain from the config, causing cookies set by the middleware (e.g., after Passkey authentication) to lack the `domain` attribute even when cross-subdomain cookies were configured.
92
+
93
+ **Fix:** The middleware now reads the domain via `CoreBetterAuthService.getCookieDomain()`, consistent with the controller.
94
+
95
+ ### 3. Fixed `auto-install-peers` in `.npmrc`
96
+
97
+ Added `auto-install-peers=false` to `.npmrc` to prevent pnpm from automatically installing peer dependencies. This ensures consistent lockfile generation and aligns with the project's fixed-version package management strategy.
98
+
99
+ This fix is internal to the package development workflow and does not affect consuming projects.
100
+
101
+ ---
102
+
103
+ ## Compatibility Notes
104
+
105
+ - All existing configurations continue to work without changes
106
+ - The `options.advanced` passthrough still works as before — the new `crossSubDomainCookies` top-level property is an alternative, not a replacement
107
+ - When both `crossSubDomainCookies` (top-level) and `options.advanced.crossSubDomainCookies` are set, the top-level value is applied first, then `options.advanced` is deep-merged on top (so the passthrough wins)
108
+ - No changes to authentication behavior unless `crossSubDomainCookies` is explicitly configured
109
+
110
+ ---
111
+
112
+ ## Module Documentation
113
+
114
+ ### Better-Auth Module
115
+
116
+ - **README:** [src/core/modules/better-auth/README.md](../src/core/modules/better-auth/README.md)
117
+ - **Integration Checklist:** [src/core/modules/better-auth/INTEGRATION-CHECKLIST.md](../src/core/modules/better-auth/INTEGRATION-CHECKLIST.md)
118
+ - **Reference Implementation:** `src/server/modules/auth/`
@@ -0,0 +1,497 @@
1
+ # Migration Guide: 11.15.x → 11.16.0
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | `IServerOptions.security.mapAndValidatePipe` type changed from `boolean` to `boolean \| { nonWhitelistedFields?: 'strip' \| 'error' \| false }` (TypeScript only — runtime is backward compatible). Input properties without `@UnifiedField` are now **silently stripped** by default. |
8
+ | **New Features** | `@UnifiedField({ exclude })` option, automatic input property whitelisting, new error code `LTNS_0303` |
9
+ | **Bugfixes** | None |
10
+ | **Migration Effort** | Low (~5-15 minutes) — update package, migrate any `@Field`-decorated input properties to `@UnifiedField`, verify build |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ ```bash
17
+ # Update package
18
+ npm install @lenne.tech/nest-server@11.16.1
19
+
20
+ # Verify build
21
+ npm run build
22
+
23
+ # Run tests
24
+ npm test
25
+ ```
26
+
27
+ > **Important:** If your input classes mix `@Field`/`@IsOptional` with `@UnifiedField` (from parent classes), those `@Field`-only properties will now be silently stripped from REST requests. See [Migration Steps](#detailed-migration-steps) below.
28
+
29
+ ---
30
+
31
+ ## What's New in 11.16.0
32
+
33
+ ### 1. Automatic Input Property Whitelisting
34
+
35
+ The `MapAndValidatePipe` now automatically handles input properties that are **not** decorated with `@UnifiedField`. This closes a security gap where properties without `@UnifiedField` (e.g. `type`, `id`, `roles`) were silently accepted in REST request bodies.
36
+
37
+ **How it works:**
38
+
39
+ 1. When a request arrives, the pipe checks which properties are decorated with `@UnifiedField` on the input class (including inherited fields).
40
+ 2. Any property **not** in the whitelist is handled according to the configured mode.
41
+ 3. The check applies recursively to nested objects and arrays.
42
+
43
+ **Three modes available:**
44
+
45
+ | Mode | Behavior | Use Case |
46
+ |------|----------|----------|
47
+ | `'strip'` (default) | Silently removes non-whitelisted properties | Production — tolerant to extra fields |
48
+ | `'error'` | Throws `BadRequestException` (`LTNS_0303`) | Development — strict validation, helps find issues |
49
+ | `false` | Disabled — all properties pass through | Legacy behavior, backward compatible |
50
+
51
+ **Configuration:**
52
+
53
+ ```typescript
54
+ // config.env.ts
55
+
56
+ // Default behavior (strip) — no config change needed
57
+ security: {
58
+ mapAndValidatePipe: true,
59
+ }
60
+
61
+ // Explicit strip (same as default)
62
+ security: {
63
+ mapAndValidatePipe: {
64
+ nonWhitelistedFields: 'strip',
65
+ },
66
+ }
67
+
68
+ // Error mode — throws BadRequestException for non-whitelisted properties
69
+ security: {
70
+ mapAndValidatePipe: {
71
+ nonWhitelistedFields: 'error',
72
+ },
73
+ }
74
+
75
+ // Disable whitelist check entirely (legacy behavior)
76
+ security: {
77
+ mapAndValidatePipe: {
78
+ nonWhitelistedFields: false,
79
+ },
80
+ }
81
+ ```
82
+
83
+ **Scope of the check:**
84
+ - Only applies to classes with **at least one** `@UnifiedField` decorator (directly or inherited)
85
+ - Classes without any `@UnifiedField` are skipped entirely (e.g. pure `class-validator` classes)
86
+ - Recursion into nested objects uses `nestedTypeRegistry` (populated by `@UnifiedField({ type: () => NestedType })`)
87
+ - Nested objects without a registry entry are not recursed into (only top-level keys are checked)
88
+
89
+ **What is excluded from the check:**
90
+ - **Custom decorator parameters** (`@CurrentUser()`, `@RESTServiceOptions()`, `@GraphQLServiceOptions()`, etc.) are always passed through unchanged — they inject framework values, not user input, and must never be mutated by the pipe
91
+ - **Basic types** (`String`, `Number`, `Boolean`, `Array`, `Object`, `Buffer`, `ArrayBuffer`) cause an early return — no validation and no whitelist check (e.g. `@Param('id') id: string`)
92
+
93
+ ### 2. `@UnifiedField({ exclude })` Option
94
+
95
+ New option to explicitly control whether a property is included in the input whitelist. This enables inheritance-safe field exclusion.
96
+
97
+ ```typescript
98
+ // Exclude a property — rejected/stripped from input, hidden from GraphQL & Swagger
99
+ @UnifiedField({ exclude: true })
100
+ override type?: TypeEnum;
101
+
102
+ // Explicitly re-enable a property excluded by a parent class
103
+ @UnifiedField({ exclude: false, description: 'Type override', isOptional: true })
104
+ override type?: TypeEnum;
105
+ ```
106
+
107
+ **When `exclude: true` is set:**
108
+ - Property is registered in `EXCLUDED_FIELD_KEYS` metadata
109
+ - `@HideField()` (GraphQL) and `@ApiHideProperty()` (Swagger) are applied automatically
110
+ - No other decorators (`@Field`, `@ApiProperty`, validators) are applied
111
+ - Property is removed from the whitelist → stripped/rejected at runtime
112
+
113
+ **When `exclude: false` is set:**
114
+ - Property is registered in `FORCE_INCLUDED_FIELD_KEYS` metadata
115
+ - Can override a parent's `exclude: true` (explicit re-enable)
116
+ - Normal `@UnifiedField` decorators are applied (`@Field`, `@ApiProperty`, validators)
117
+
118
+ ### 3. Inheritance Priority Model
119
+
120
+ The whitelist system uses a 3-level priority model when resolving fields across class hierarchies:
121
+
122
+ | Priority | Setting | Behavior |
123
+ |----------|---------|----------|
124
+ | 1 (highest) | `exclude: true` / `exclude: false` | Explicit — resolved by nearest class (child wins) |
125
+ | 2 | `@UnifiedField()` (no `exclude`) | Implicit inclusion — **cannot** override explicit `exclude: true` |
126
+ | 3 (lowest) | No decorator | Not in whitelist |
127
+
128
+ **Key rule:** An implicit `@UnifiedField()` in a child class **cannot** override a parent's `exclude: true`. Only `@UnifiedField({ exclude: false })` can re-enable an excluded field.
129
+
130
+ #### Full Inheritance Table
131
+
132
+ | Parent Decorator | Child Decorator | Field in Parent Input | Field in Child Input |
133
+ |-----------------|-----------------|----------------------|---------------------|
134
+ | `@UnifiedField()` | _(inherited, no override)_ | ACCEPTED | ACCEPTED |
135
+ | `@UnifiedField()` | `@UnifiedField()` | ACCEPTED | ACCEPTED |
136
+ | `@UnifiedField()` | `@UnifiedField({ exclude: true })` | ACCEPTED | **REJECTED** |
137
+ | `@UnifiedField()` | `@UnifiedField({ exclude: false })` | ACCEPTED | ACCEPTED |
138
+ | `@UnifiedField({ exclude: true })` | _(inherited, no override)_ | REJECTED | REJECTED |
139
+ | `@UnifiedField({ exclude: true })` | `@UnifiedField()` _(implicit)_ | REJECTED | **REJECTED** |
140
+ | `@UnifiedField({ exclude: true })` | `@UnifiedField({ exclude: true })` | REJECTED | REJECTED |
141
+ | `@UnifiedField({ exclude: true })` | `@UnifiedField({ exclude: false })` | REJECTED | **ACCEPTED** |
142
+ | `@UnifiedField({ exclude: false })` | _(inherited, no override)_ | ACCEPTED | ACCEPTED |
143
+ | `@UnifiedField({ exclude: false })` | `@UnifiedField({ exclude: true })` | ACCEPTED | **REJECTED** |
144
+ | _(no decorator)_ | `@UnifiedField()` | — | ACCEPTED |
145
+ | _(no decorator)_ | `@UnifiedField({ exclude: true })` | — | REJECTED |
146
+ | _(no decorator)_ | `@UnifiedField({ exclude: false })` | — | ACCEPTED |
147
+
148
+ > **Reading the table:** "Parent Input" = behavior when the parent class is used as input type. "Child Input" = behavior when the child class is used as input type.
149
+
150
+ #### Practical Example
151
+
152
+ ```typescript
153
+ // CoreUserInput (from nest-server)
154
+ class CoreUserInput extends CoreInput {
155
+ @UnifiedField({ description: 'Email', isOptional: true })
156
+ email?: string; // ← ACCEPTED everywhere
157
+
158
+ @UnifiedField({ description: 'First name', isOptional: true })
159
+ firstName?: string; // ← ACCEPTED everywhere
160
+ }
161
+
162
+ // UserInput (your project) — exclude 'type' from REST input
163
+ class UserInput extends CoreUserInput {
164
+ @UnifiedField({ exclude: true })
165
+ override type?: TypeEnum; // ← REJECTED: stripped/error in REST
166
+
167
+ @UnifiedField({ description: 'Job Title', isOptional: true })
168
+ jobTitle?: string; // ← ACCEPTED: new field in child
169
+ }
170
+
171
+ // AdminUserInput (special override) — re-enable 'type' for admins
172
+ class AdminUserInput extends UserInput {
173
+ @UnifiedField({ exclude: false, description: 'Type', isOptional: true })
174
+ override type?: TypeEnum; // ← ACCEPTED: explicit re-enable
175
+ }
176
+ ```
177
+
178
+ ### 4. New Error Code: LTNS_0303
179
+
180
+ Available when using `nonWhitelistedFields: 'error'` mode:
181
+
182
+ | Code | Message | Translation (DE) | Translation (EN) |
183
+ |------|---------|-----------------|-----------------|
184
+ | `LTNS_0303` | Non-whitelisted properties found | Die folgenden Eigenschaften sind nicht erlaubt: {{properties}}. Nur mit @UnifiedField dekorierte Eigenschaften werden akzeptiert. | The following properties are not allowed: {{properties}}. Only properties decorated with @UnifiedField are accepted. |
185
+
186
+ Error response format:
187
+ ```json
188
+ {
189
+ "statusCode": 400,
190
+ "message": "#LTNS_0303: Non-whitelisted properties found [evilProp, nested.badField]"
191
+ }
192
+ ```
193
+
194
+ ### 5. Nested Object & Array Support
195
+
196
+ The whitelist check recurses into nested objects and arrays:
197
+
198
+ ```typescript
199
+ class AddressInput {
200
+ @UnifiedField({ description: 'Street', isOptional: true })
201
+ street?: string;
202
+
203
+ @UnifiedField({ description: 'City', isOptional: true })
204
+ city?: string;
205
+ }
206
+
207
+ class UserInput extends CoreUserInput {
208
+ @UnifiedField({ description: 'Address', isOptional: true, type: () => AddressInput })
209
+ address?: AddressInput;
210
+ }
211
+
212
+ // Request body:
213
+ {
214
+ "firstName": "John",
215
+ "address": {
216
+ "street": "Main St",
217
+ "city": "Berlin",
218
+ "malicious": "injected" // ← stripped (or error: "address.malicious")
219
+ }
220
+ }
221
+ ```
222
+
223
+ For arrays, each element is checked individually:
224
+ ```json
225
+ {
226
+ "items": [
227
+ { "name": "ok" },
228
+ { "name": "ok", "evil": "hack" } // ← items[1].evil stripped/rejected
229
+ ]
230
+ }
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Breaking Changes
236
+
237
+ ### Properties Without `@UnifiedField` Are Now Stripped
238
+
239
+ **This is the most important change.** If your input classes have properties decorated with `@Field` + `@IsOptional` (the old pattern) but inherit from a parent that uses `@UnifiedField`, those properties will be silently stripped from REST requests.
240
+
241
+ **Before (11.15.x):**
242
+ ```typescript
243
+ // This worked — jobTitle was accepted via REST
244
+ @InputType()
245
+ export class UserInput extends CoreUserInput {
246
+ @Field(() => String, { nullable: true })
247
+ @IsOptional()
248
+ jobTitle?: string;
249
+ }
250
+ ```
251
+
252
+ **After (11.16.0):**
253
+ ```typescript
254
+ // jobTitle is now stripped because CoreUserInput uses @UnifiedField
255
+ // and jobTitle is not decorated with @UnifiedField
256
+
257
+ // Fix: migrate to @UnifiedField
258
+ @InputType()
259
+ export class UserInput extends CoreUserInput {
260
+ @UnifiedField({ description: 'Job Title', isOptional: true })
261
+ jobTitle?: string;
262
+ }
263
+ ```
264
+
265
+ ### TypeScript Interface Change: `mapAndValidatePipe`
266
+
267
+ The `mapAndValidatePipe` property type in `IServerOptions.security` changed from:
268
+
269
+ ```typescript
270
+ // Before (11.15.x)
271
+ mapAndValidatePipe?: boolean;
272
+ ```
273
+
274
+ To:
275
+
276
+ ```typescript
277
+ // After (11.16.0)
278
+ mapAndValidatePipe?:
279
+ | boolean
280
+ | {
281
+ nonWhitelistedFields?: 'strip' | 'error' | false;
282
+ };
283
+ ```
284
+
285
+ **Impact:** This is a **TypeScript-only** change. All existing `boolean` configurations remain valid. Code that directly accesses `mapAndValidatePipe` as a boolean may need type narrowing.
286
+
287
+ ---
288
+
289
+ ## Detailed Migration Steps
290
+
291
+ ### Step 1: Update Package
292
+
293
+ ```bash
294
+ npm install @lenne.tech/nest-server@11.16.1
295
+ ```
296
+
297
+ ### Step 2: Find Input Properties Using Old `@Field` Pattern
298
+
299
+ Search for input classes that extend Core* classes but use `@Field` instead of `@UnifiedField`:
300
+
301
+ ```bash
302
+ # Find input files using @Field directly
303
+ grep -rn "@Field(" src/server/modules/*/inputs/ --include="*.ts"
304
+ ```
305
+
306
+ ### Step 3: Migrate `@Field` Properties to `@UnifiedField`
307
+
308
+ For each property found:
309
+
310
+ **Before:**
311
+ ```typescript
312
+ import { Field, InputType } from '@nestjs/graphql';
313
+ import { IsOptional } from 'class-validator';
314
+ import { Restricted } from '@lenne.tech/nest-server';
315
+
316
+ @Field(() => String, { description: 'Job Title', nullable: true })
317
+ @IsOptional()
318
+ @Restricted(RoleEnum.ADMIN)
319
+ jobTitle?: string;
320
+ ```
321
+
322
+ **After:**
323
+ ```typescript
324
+ import { UnifiedField } from '@lenne.tech/nest-server';
325
+
326
+ @UnifiedField({
327
+ description: 'Job Title',
328
+ isOptional: true,
329
+ roles: RoleEnum.ADMIN,
330
+ })
331
+ jobTitle?: string;
332
+ ```
333
+
334
+ **Mapping table:**
335
+
336
+ | Old Pattern | `@UnifiedField` Equivalent |
337
+ |------------|---------------------------|
338
+ | `@Field(() => String, { nullable: true })` | `isOptional: true` |
339
+ | `@Field(() => [String])` | `isArray: true, type: () => String` |
340
+ | `@IsOptional()` | `isOptional: true` |
341
+ | `@IsEnum(MyEnum)` | `enum: { enum: MyEnum }` |
342
+ | `@Restricted(RoleEnum.ADMIN)` | `roles: RoleEnum.ADMIN` |
343
+ | `@ValidateNested()` + `@Type(() => X)` | `type: () => X` (automatic) |
344
+
345
+ ### Step 4: Exclude Properties That Should Not Be Set via Input
346
+
347
+ If you have properties that should never be set via REST (e.g. `type`, `roles`, `id`):
348
+
349
+ ```typescript
350
+ @UnifiedField({ exclude: true })
351
+ override type?: TypeEnum;
352
+ ```
353
+
354
+ ### Step 5: Verify Build and Tests
355
+
356
+ ```bash
357
+ npm run build
358
+ npm test
359
+ ```
360
+
361
+ ### Step 6: Choose Whitelist Mode (Optional)
362
+
363
+ For development/staging, consider using `'error'` mode to catch issues early:
364
+
365
+ ```typescript
366
+ // config.env.ts (development only)
367
+ security: {
368
+ mapAndValidatePipe: {
369
+ nonWhitelistedFields: 'error',
370
+ },
371
+ }
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Compatibility Notes
377
+
378
+ ### Existing Projects Using `mapAndValidatePipe: true`
379
+
380
+ No configuration change needed. The default `'strip'` mode activates automatically.
381
+
382
+ ### Existing Projects Using `mapAndValidatePipe: false`
383
+
384
+ The pipe is not registered at all, so no whitelist check runs. No change needed.
385
+
386
+ ### Projects Without `@UnifiedField`
387
+
388
+ If your input classes do not use `@UnifiedField` at all (neither directly nor inherited), the whitelist check is skipped entirely (`whitelistedKeys.size === 0` → no check). This ensures backward compatibility for pure `class-validator` setups.
389
+
390
+ ### GraphQL vs REST
391
+
392
+ The whitelist check runs in `MapAndValidatePipe`, which processes both REST and GraphQL inputs. However, GraphQL already enforces its schema strictly — unknown fields cause GraphQL validation errors before the pipe runs. The primary benefit is for **REST endpoints** where JSON bodies accept arbitrary properties.
393
+
394
+ ### `@HideField` and `@ApiHideProperty` with `exclude: true`
395
+
396
+ When using `@UnifiedField({ exclude: true })`, the decorator automatically applies `@HideField()` (GraphQL) and `@ApiHideProperty()` (Swagger). This means the field is hidden from both the GraphQL schema and the Swagger documentation, in addition to being stripped/rejected at runtime.
397
+
398
+ ---
399
+
400
+ ## Troubleshooting
401
+
402
+ ### Property Suddenly Returning `null` After Update
403
+
404
+ **Cause:** The property uses `@Field` instead of `@UnifiedField`, and its parent class uses `@UnifiedField`. The property is now being stripped.
405
+
406
+ **Solution:** Migrate the property to `@UnifiedField`:
407
+ ```typescript
408
+ // Before
409
+ @Field(() => String, { nullable: true })
410
+ @IsOptional()
411
+ jobTitle?: string;
412
+
413
+ // After
414
+ @UnifiedField({ description: 'Job Title', isOptional: true })
415
+ jobTitle?: string;
416
+ ```
417
+
418
+ ### `BadRequestException` with `LTNS_0303` in Production
419
+
420
+ **Cause:** `nonWhitelistedFields` is set to `'error'` and the client sends extra properties.
421
+
422
+ **Solution:** Either:
423
+ 1. Fix the client to only send whitelisted properties
424
+ 2. Switch to `'strip'` mode (default): `nonWhitelistedFields: 'strip'`
425
+
426
+ ### Child Class Cannot Override Parent's `exclude: true`
427
+
428
+ **Cause:** Using implicit `@UnifiedField()` (without `exclude: false`) cannot override a parent's `exclude: true`.
429
+
430
+ **Solution:** Use explicit `@UnifiedField({ exclude: false })`:
431
+ ```typescript
432
+ // This does NOT work — implicit cannot override
433
+ @UnifiedField({ description: 'Type', isOptional: true })
434
+ override type?: TypeEnum; // Still rejected!
435
+
436
+ // This works — explicit re-enable
437
+ @UnifiedField({ exclude: false, description: 'Type', isOptional: true })
438
+ override type?: TypeEnum; // Accepted
439
+ ```
440
+
441
+ ### Nested Object Properties Not Being Checked
442
+
443
+ **Cause:** The nested type is not registered in `nestedTypeRegistry`. This happens when using `Record<string, any>` or `object` type without specifying `type: () => NestedClass`.
444
+
445
+ **Solution:** Use explicit type in `@UnifiedField`:
446
+ ```typescript
447
+ // No recursive check (metadata is Record<string, any>)
448
+ @UnifiedField({ description: 'Metadata', isOptional: true })
449
+ metadata?: Record<string, any>;
450
+
451
+ // Recursive check enabled
452
+ @UnifiedField({ description: 'Address', isOptional: true, type: () => AddressInput })
453
+ address?: AddressInput;
454
+ ```
455
+
456
+ ---
457
+
458
+ ## Module Documentation
459
+
460
+ ### MapAndValidatePipe (Updated)
461
+
462
+ - **Key File:** [src/core/common/pipes/map-and-validate.pipe.ts](../src/core/common/pipes/map-and-validate.pipe.ts)
463
+ - **Changes:**
464
+ - `processNonWhitelistedFields()` — recursive whitelist enforcement
465
+ - `nonWhitelistedFieldsMode` — configurable via `ConfigService`
466
+ - New import of `ErrorCode` for `LTNS_0303` error messages
467
+
468
+ ### UnifiedField Decorator (Updated)
469
+
470
+ - **Key File:** [src/core/common/decorators/unified-field.decorator.ts](../src/core/common/decorators/unified-field.decorator.ts)
471
+ - **Changes:**
472
+ - New symbols: `UNIFIED_FIELD_KEYS`, `EXCLUDED_FIELD_KEYS`, `FORCE_INCLUDED_FIELD_KEYS`
473
+ - New option: `exclude?: boolean`
474
+ - New function: `getUnifiedFieldKeys(metatype)` — resolves whitelist with inheritance
475
+ - `exclude: true` applies `@HideField()` + `@ApiHideProperty()` automatically
476
+
477
+ ### Error Codes (Updated)
478
+
479
+ - **Key File:** [src/core/modules/error-code/error-codes.ts](../src/core/modules/error-code/error-codes.ts)
480
+ - **Documentation:** [docs/error-codes.md](../docs/error-codes.md)
481
+ - **Changes:** New error code `LTNS_0303: NON_WHITELISTED_PROPERTIES`
482
+
483
+ ### Unit Tests
484
+
485
+ - **Key File:** [tests/unit/unified-field-whitelist.spec.ts](../tests/unit/unified-field-whitelist.spec.ts)
486
+ - **Coverage:** 49 tests covering all modes, inheritance, nesting, config variants, custom decorator parameters, edge cases
487
+
488
+ ---
489
+
490
+ ## References
491
+
492
+ - [IServerOptions Interface](../src/core/common/interfaces/server-options.interface.ts) — Updated `mapAndValidatePipe` type
493
+ - [UnifiedField Decorator](../src/core/common/decorators/unified-field.decorator.ts) — `exclude` option and metadata tracking
494
+ - [MapAndValidatePipe](../src/core/common/pipes/map-and-validate.pipe.ts) — Whitelist enforcement implementation
495
+ - [Error Codes](../src/core/modules/error-code/error-codes.ts) — `LTNS_0303` definition
496
+ - [Unit Tests](../tests/unit/unified-field-whitelist.spec.ts) — Comprehensive test coverage
497
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) — Reference implementation