@lenne.tech/nest-server 11.26.2 → 11.26.3

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.
@@ -0,0 +1,186 @@
1
+ # Migration Guide: 11.26.2 → 11.26.3
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **Bugfixes** | `@UnifiedField({ enum: … })` no longer emits a broken, unnamed `$ref` in the generated OpenAPI document under `@nestjs/swagger >= 11.4` — enum fields now produce proper named component schemas (or clean inline enums when `enumName: null` / auto-detection fails) instead of `allOf: [{ $ref: '#/components/schemas/' }]`, which crashed OpenAPI client generators like `@hey-api/openapi-ts` |
9
+ | **New Features** | None |
10
+ | **Migration Effort** | 0 minutes (automatic) — drop-in patch release |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ No code changes required. The fix applies automatically as soon as the package is updated.
17
+
18
+ ```bash
19
+ # Update package
20
+ pnpm add @lenne.tech/nest-server@11.26.3
21
+
22
+ # Verify build
23
+ pnpm run build
24
+
25
+ # Run tests
26
+ pnpm test
27
+
28
+ # (Optional) regenerate REST client SDK against the new OpenAPI document
29
+ pnpm --filter @your-app/api-sdk openapi-ts
30
+ ```
31
+
32
+ ---
33
+
34
+ ## What's Fixed in 11.26.3
35
+
36
+ ### OpenAPI: broken empty `$ref` on enum-typed `@UnifiedField` properties
37
+
38
+ **Affects:** Any project that
39
+
40
+ - consumes `@lenne.tech/nest-server` together with `@nestjs/swagger >= 11.4` (this repo pinned to `11.4.2`), AND
41
+ - exposes REST endpoints whose DTOs declare enum fields via `@UnifiedField({ enum: … })`, AND
42
+ - runs an OpenAPI client generator (e.g. `@hey-api/openapi-ts`, `openapi-typescript`, `openapi-generator-cli`) against the bundled OpenAPI document.
43
+
44
+ **Symptom (before 11.26.3):**
45
+
46
+ The decorator passed `type: () => String` to `@nestjs/swagger` ALONGSIDE `enum` + `enumName`. `@nestjs/swagger <= 11.2` silently tolerated the combination, but `@nestjs/swagger >= 11.4` emits a broken, **unnamed** enum reference and never registers the enum under `components.schemas`:
47
+
48
+ ```jsonc
49
+ // Generated OpenAPI document — BROKEN
50
+ {
51
+ "components": {
52
+ "schemas": {
53
+ "SomeInput": {
54
+ "properties": {
55
+ "status": {
56
+ "allOf": [
57
+ { "$ref": "#/components/schemas/" } // ← empty target!
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ // ← StatusEnum is missing entirely from components.schemas
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ Downstream tools crash:
69
+
70
+ ```
71
+ @hey-api/openapi-ts: Missing $ref pointer "#/components/schemas/". Token "" does not exist.
72
+ ```
73
+
74
+ **Fix (in 11.26.3):**
75
+
76
+ `@UnifiedField` no longer sets `swaggerOpts.type` when the field is an enum. `@nestjs/swagger` derives the schema from `enum` + `enumName` correctly:
77
+
78
+ ```jsonc
79
+ // Generated OpenAPI document — CORRECT
80
+ {
81
+ "components": {
82
+ "schemas": {
83
+ "StatusEnum": { "type": "string", "enum": ["draft", "published", "review"] },
84
+ "SomeInput": {
85
+ "properties": {
86
+ "status": {
87
+ "allOf": [
88
+ { "$ref": "#/components/schemas/StatusEnum" } // ← named, resolvable
89
+ ]
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Behaviour matrix:**
99
+
100
+ | `@UnifiedField` form | OpenAPI output before 11.26.3 | OpenAPI output in 11.26.3 |
101
+ |---|---|---|
102
+ | `{ enum: MyEnum, enumName: 'MyEnum' }` | Empty `$ref`, `MyEnum` missing from `components.schemas` | Named `MyEnum` schema, property uses `$ref` |
103
+ | `{ enum: MyEnum }` + `registerEnum(MyEnum, { name: 'MyEnum' })` | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` |
104
+ | `{ enum: MyEnum, enumName: null }` (opt out) | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
105
+ | `{ enum: MyEnum }` without registration | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
106
+ | Long-form `{ enum: { enum: MyEnum, enumName: 'MyEnum' } }` (deprecated) | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` (deprecation warning unchanged) |
107
+ | Non-enum fields (`String`, `Number`, `Date`, custom classes, …) | Unchanged | Unchanged |
108
+
109
+ GraphQL schema, class-validator runtime validation (`IsEnum`), Mongoose `@Prop`, and field-level `@Restricted` / `@Roles` behaviour are all bit-for-bit identical to 11.26.2.
110
+
111
+ ---
112
+
113
+ ## Compatibility Notes
114
+
115
+ - **`@nestjs/swagger <= 11.2` consumers:** The previously emitted `type: () => String` was redundant; removing it produces the same enum schema as before. No observable change.
116
+ - **`@nestjs/swagger >= 11.4` consumers:** The OpenAPI document for enum fields changes from a **broken** empty `$ref` to a **correct** named schema (or clean inline enum). This is strictly a defect fix — any client generator that was previously crashing now succeeds.
117
+ - **OpenAPI client generators / SDK consumers:** After updating, regenerate the SDK once. Enum properties that were previously typed `string` (when the generator silently dropped the broken `$ref`) will now be typed as the proper enum union — review the generated SDK once and adjust call-sites if you relied on the loose `string` type.
118
+ - **GraphQL consumers:** No change. The `Field(...)` factory and enum resolution are untouched.
119
+ - **`@UnifiedField` public API:** Unchanged. All option shapes (`enum: MyEnum`, `enumName`, deprecated long-form `{ enum: { … } }`, `enumName: null`) keep their documented semantics.
120
+ - **Mongoose / class-validator:** Unchanged. `@Prop({ type: baseType })` still applied for enum fields; `IsEnum(...)` is still the authoritative validator.
121
+ - **Vendor-mode consumers:** Same fix lands in `src/core/common/decorators/unified-field.decorator.ts`. Sync via `/lt-dev:backend:update-nest-server-core`. No flatten-fix change required.
122
+ - **Hidden / excluded enum fields (`@UnifiedField({ exclude: true })`):** Unaffected — those still hide from the OpenAPI document via `ApiHideProperty()`.
123
+
124
+ ---
125
+
126
+ ## Verifying the Fix
127
+
128
+ If you previously hit the empty-`$ref` defect, confirm the regenerated OpenAPI document:
129
+
130
+ ```bash
131
+ # 1. Boot the API and dump the OpenAPI document
132
+ pnpm start &
133
+ curl -s http://localhost:3000/api-json > /tmp/openapi.json
134
+
135
+ # 2. There must be no empty/unnamed component refs
136
+ grep -F '"$ref": "#/components/schemas/"' /tmp/openapi.json && echo 'BROKEN' || echo 'OK'
137
+
138
+ # 3. Every enum used in a DTO must appear in components.schemas
139
+ jq '.components.schemas | keys' /tmp/openapi.json
140
+ ```
141
+
142
+ A complete regression test ships in `tests/unified-field-enum-swagger.e2e-spec.ts` and inspects the real document built by `SwaggerModule.createDocument()` for:
143
+
144
+ - no empty `$ref` anywhere in the document,
145
+ - a named component schema per enum (string / numeric / array / auto-detected / deprecated long-form),
146
+ - correct enum values and property references,
147
+ - inline-enum fallback for `enumName: null` and for unregistered enums (no empty `$ref`).
148
+
149
+ ---
150
+
151
+ ## Troubleshooting
152
+
153
+ ### After updating, my generated SDK still has an empty `$ref` error
154
+
155
+ Make sure the SDK is regenerated against a **freshly rebuilt** API. Stale `openapi.json` artefacts checked into the consumer repo continue to be broken. Rebuild the API (`pnpm run build`) and re-export the document (`/api-json` or `SwaggerModule.createDocument` snapshot) before running your codegen.
156
+
157
+ ### My enum properties used to be typed `string` in the generated client and now they're a strict union
158
+
159
+ That is the corrected behaviour — the previous client was generated against a broken document and silently widened the type. Update call-sites to use the enum union (or import the enum from your shared package). If you need the loose `string` type during the rollout, your codegen typically offers an `--enum-style` flag (e.g. `@hey-api/openapi-ts` → `enums: 'javascript'`) to keep the old shape.
160
+
161
+ ### I rely on the deprecated long-form `enum: { enum: MyEnum, enumName: 'MyEnum' }`
162
+
163
+ It still works and now produces the same named schema as the shortcut form. The deprecation warning emitted at decoration time is unchanged. Plan to migrate to the shortcut form (`enum: MyEnum, enumName: 'MyEnum'`) before a future MINOR removes the long form.
164
+
165
+ ---
166
+
167
+ ## Module Documentation
168
+
169
+ ### Core Common — `@UnifiedField`
170
+
171
+ - **Decorator:** `src/core/common/decorators/unified-field.decorator.ts`
172
+ - **Architecture notes:** [.claude/rules/architecture.md](../.claude/rules/architecture.md) (Input Validation section)
173
+ - **Reference tests:**
174
+ - `tests/unified-field-enum-swagger.e2e-spec.ts` — OpenAPI schema regression guard (the contract this release restores)
175
+ - `tests/unified-field-enum.e2e-spec.ts` — metadata-level enum behaviour
176
+ - `tests/unified-field-enum-api.e2e-spec.ts` — runtime REST/GraphQL enum behaviour
177
+
178
+ ---
179
+
180
+ ## References
181
+
182
+ - [Migration Guide 11.26.1 → 11.26.2](./11.26.1-to-11.26.2.md) — Previous release (`COOKIE_PREFIX` env, cross-layer cookie-prefix lockstep)
183
+ - [Architecture rules — Input Validation](../.claude/rules/architecture.md)
184
+ - [@nestjs/swagger 11.4 release notes](https://github.com/nestjs/swagger/releases) — context for the schema-emission change that surfaced the latent defect
185
+ - [@hey-api/openapi-ts](https://heyapi.dev/) — one of the OpenAPI client generators that was crashing on the broken document
186
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.26.2",
3
+ "version": "11.26.3",
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",
@@ -408,13 +408,19 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
408
408
  swaggerOpts.required = true;
409
409
  }
410
410
 
411
- // Set type for swagger
412
- if (baseType) {
413
- if (normalizedEnum) {
414
- swaggerOpts.type = () => String;
415
- } else {
416
- swaggerOpts.type = baseType;
417
- }
411
+ // Set type for swagger.
412
+ //
413
+ // For enum fields we deliberately do NOT set `type`: @nestjs/swagger derives
414
+ // the schema from `enum` + `enumName` (set further below). Passing
415
+ // `type: () => String` ALONGSIDE `enum`/`enumName` makes @nestjs/swagger
416
+ // >= 11.4 emit a broken, UNNAMED enum reference
417
+ // (`allOf: [{ $ref: '#/components/schemas/' }]`) and never adds the enum to
418
+ // `components.schemas`. That crashes OpenAPI client generators — e.g.
419
+ // @hey-api/openapi-ts fails with «Missing $ref pointer "#/components/schemas/"».
420
+ // (On @nestjs/swagger <= 11.2 the extra `type` was tolerated, which is why
421
+ // this only surfaced after a swagger bump.)
422
+ if (baseType && !normalizedEnum) {
423
+ swaggerOpts.type = baseType;
418
424
  }
419
425
 
420
426
  // Set description