nesties 1.1.15 → 1.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -56
- package/dist/index.cjs +240 -93
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +230 -83
- package/dist/index.mjs.map +4 -4
- package/dist/src/i18n-module/i18n-factory.d.ts +2 -2
- package/dist/src/i18n-module/i18n-module.options.d.ts +2 -2
- package/dist/src/i18n-module/locale.pipe.d.ts +3 -3
- package/dist/src/resolver.d.ts +62 -6
- package/dist/src/token.guard.d.ts +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Nest.js utilities**
|
|
4
4
|
|
|
5
|
-
Nesties is a utility library for Nest.js applications, designed to simplify and enhance common patterns such as decorators, response structures, and
|
|
5
|
+
Nesties is a utility library for Nest.js applications, designed to simplify and enhance common patterns such as decorators, response structures, request validation, and HTTP-level concerns such as tokens and i18n. This library provides a set of utilities to streamline your development workflow and improve code reuse and clarity when working with Nest.js.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -10,7 +10,10 @@ Nesties is a utility library for Nest.js applications, designed to simplify and
|
|
|
10
10
|
- **Predefined API Responses**: Simplified and consistent response structures for APIs.
|
|
11
11
|
- **Data Validation Pipes**: Validation pipe utilities to handle query and body validation effortlessly.
|
|
12
12
|
- **Custom Guards**: Easily implement token-based guards and API header validation.
|
|
13
|
+
- **ParamResolver utilities**: Strongly-typed access to headers and query parameters, including dynamic and request-scoped resolvers.
|
|
13
14
|
- **Pagination and Return DTOs**: DTOs for standard and paginated API responses.
|
|
15
|
+
- **AbortableModule**: Request-lifetime-aware abort signals for long-running work.
|
|
16
|
+
- **I18nModule**: Locale-aware translation for response DTOs and strings.
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
@@ -34,7 +37,7 @@ Nesties allows you to merge multiple decorators of the same type (property, meth
|
|
|
34
37
|
|
|
35
38
|
- **Property Decorator**
|
|
36
39
|
|
|
37
|
-
```
|
|
40
|
+
```ts
|
|
38
41
|
import { MergePropertyDecorators } from 'nesties';
|
|
39
42
|
|
|
40
43
|
const CombinedPropertyDecorator = MergePropertyDecorators([Decorator1, Decorator2]);
|
|
@@ -42,7 +45,7 @@ const CombinedPropertyDecorator = MergePropertyDecorators([Decorator1, Decorator
|
|
|
42
45
|
|
|
43
46
|
- **Method Decorator**
|
|
44
47
|
|
|
45
|
-
```
|
|
48
|
+
```ts
|
|
46
49
|
import { MergeMethodDecorators } from 'nesties';
|
|
47
50
|
|
|
48
51
|
const CombinedMethodDecorator = MergeMethodDecorators([Decorator1, Decorator2]);
|
|
@@ -50,7 +53,7 @@ const CombinedMethodDecorator = MergeMethodDecorators([Decorator1, Decorator2]);
|
|
|
50
53
|
|
|
51
54
|
- **Class Decorator**
|
|
52
55
|
|
|
53
|
-
```
|
|
56
|
+
```ts
|
|
54
57
|
import { MergeClassDecorators } from 'nesties';
|
|
55
58
|
|
|
56
59
|
const CombinedClassDecorator = MergeClassDecorators([Decorator1, Decorator2]);
|
|
@@ -58,7 +61,7 @@ const CombinedClassDecorator = MergeClassDecorators([Decorator1, Decorator2]);
|
|
|
58
61
|
|
|
59
62
|
- **Parameter Decorator**
|
|
60
63
|
|
|
61
|
-
```
|
|
64
|
+
```ts
|
|
62
65
|
import { MergeParameterDecorators } from 'nesties';
|
|
63
66
|
|
|
64
67
|
const CombinedParameterDecorator = MergeParameterDecorators([Decorator1, Decorator2]);
|
|
@@ -68,7 +71,7 @@ const CombinedParameterDecorator = MergeParameterDecorators([Decorator1, Decorat
|
|
|
68
71
|
|
|
69
72
|
Nesties includes a utility for defining API error responses conveniently.
|
|
70
73
|
|
|
71
|
-
```
|
|
74
|
+
```ts
|
|
72
75
|
import { ApiError } from 'nesties';
|
|
73
76
|
|
|
74
77
|
@ApiError(401, 'Unauthorized access')
|
|
@@ -80,7 +83,7 @@ Nesties provides utilities for creating validation pipes with automatic data tra
|
|
|
80
83
|
|
|
81
84
|
- **Data Pipe**
|
|
82
85
|
|
|
83
|
-
```
|
|
86
|
+
```ts
|
|
84
87
|
import { DataPipe } from 'nesties';
|
|
85
88
|
|
|
86
89
|
const validationPipe = DataPipe();
|
|
@@ -88,13 +91,13 @@ const validationPipe = DataPipe();
|
|
|
88
91
|
|
|
89
92
|
- **Decorators for Request Validation**
|
|
90
93
|
|
|
91
|
-
```
|
|
94
|
+
```ts
|
|
92
95
|
import { DataQuery, DataBody } from 'nesties';
|
|
93
96
|
|
|
94
97
|
class ExampleController {
|
|
95
|
-
|
|
98
|
+
myMethod(@DataQuery() query: MyQueryDto, @DataBody() body: MyBodyDto) {
|
|
96
99
|
// ...
|
|
97
|
-
|
|
100
|
+
}
|
|
98
101
|
}
|
|
99
102
|
```
|
|
100
103
|
|
|
@@ -107,10 +110,19 @@ Nesties provides a set of DTOs for consistent API response structures, and it al
|
|
|
107
110
|
- **PaginatedReturnMessageDto**: For paginated responses, including metadata about pagination.
|
|
108
111
|
- **ReturnMessageDto**: A utility function for generating DTOs based on a class type.
|
|
109
112
|
|
|
110
|
-
```
|
|
111
|
-
import {
|
|
113
|
+
```ts
|
|
114
|
+
import {
|
|
115
|
+
BlankReturnMessageDto,
|
|
116
|
+
GenericReturnMessageDto,
|
|
117
|
+
PaginatedReturnMessageDto,
|
|
118
|
+
ReturnMessageDto,
|
|
119
|
+
} from 'nesties';
|
|
112
120
|
|
|
113
|
-
const response = new GenericReturnMessageDto(
|
|
121
|
+
const response = new GenericReturnMessageDto(
|
|
122
|
+
200,
|
|
123
|
+
'Operation successful',
|
|
124
|
+
myData,
|
|
125
|
+
);
|
|
114
126
|
```
|
|
115
127
|
|
|
116
128
|
#### Example Usage of `ReturnMessageDto`
|
|
@@ -119,45 +131,48 @@ const response = new GenericReturnMessageDto(200, 'Operation successful', myData
|
|
|
119
131
|
|
|
120
132
|
Suppose we have a `User` class:
|
|
121
133
|
|
|
122
|
-
```
|
|
134
|
+
```ts
|
|
123
135
|
import { ApiProperty } from '@nestjs/swagger';
|
|
124
136
|
|
|
125
137
|
class User {
|
|
126
|
-
@ApiProperty({ description: 'The unique ID of the user', type: Number })
|
|
127
|
-
id: number;
|
|
138
|
+
@ApiProperty({ description: 'The unique ID of the user', type: Number })
|
|
139
|
+
id: number;
|
|
128
140
|
|
|
129
|
-
@ApiProperty({ description: 'The name of the user', type: String })
|
|
130
|
-
name: string;
|
|
141
|
+
@ApiProperty({ description: 'The name of the user', type: String })
|
|
142
|
+
name: string;
|
|
131
143
|
|
|
132
|
-
@ApiProperty({ description: 'The email address of the user', type: String })
|
|
133
|
-
email: string;
|
|
144
|
+
@ApiProperty({ description: 'The email address of the user', type: String })
|
|
145
|
+
email: string;
|
|
134
146
|
}
|
|
135
147
|
```
|
|
136
148
|
|
|
137
149
|
You can create a return message DTO for this class:
|
|
138
150
|
|
|
139
|
-
```
|
|
151
|
+
```ts
|
|
140
152
|
import { ReturnMessageDto } from 'nesties';
|
|
141
153
|
|
|
142
154
|
class UserReturnMessageDto extends ReturnMessageDto(User) {}
|
|
143
155
|
|
|
144
|
-
const response = new UserReturnMessageDto(200, 'Success', {
|
|
156
|
+
const response = new UserReturnMessageDto(200, 'Success', {
|
|
157
|
+
id: 1,
|
|
158
|
+
name: 'John Doe',
|
|
159
|
+
email: 'john.doe@example.com',
|
|
160
|
+
});
|
|
145
161
|
```
|
|
146
162
|
|
|
147
|
-
This approach automatically creates a DTO structure with the properties of `User` integrated as the data field, ensuring consistency and reusability in your API responses.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
```
|
|
163
|
+
This approach automatically creates a DTO structure with the properties of `User` integrated as the `data` field, ensuring consistency and reusability in your API responses.
|
|
151
164
|
|
|
152
165
|
### 5. Token Guard
|
|
153
166
|
|
|
154
167
|
`TokenGuard` validates a single “server token” before invoking a controller method. By default it reads `SERVER_TOKEN` from `ConfigService` and compares it with the `x-server-token` header, returning a `401` when they differ.
|
|
155
168
|
|
|
169
|
+
Internally, `TokenGuard` uses the same resolver primitives as `ParamResolver`, so you can read tokens from headers, query parameters, or custom logic that depends on the request and `ModuleRef`.
|
|
170
|
+
|
|
156
171
|
#### Quick start (defaults only)
|
|
157
172
|
|
|
158
173
|
1. **Load the config module**
|
|
159
174
|
|
|
160
|
-
```
|
|
175
|
+
```ts
|
|
161
176
|
import { ConfigModule } from '@nestjs/config';
|
|
162
177
|
|
|
163
178
|
@Module({
|
|
@@ -168,13 +183,13 @@ This approach automatically creates a DTO structure with the properties of `User
|
|
|
168
183
|
|
|
169
184
|
2. **Set the secret**
|
|
170
185
|
|
|
171
|
-
```
|
|
186
|
+
```text
|
|
172
187
|
SERVER_TOKEN=your-secure-token
|
|
173
188
|
```
|
|
174
189
|
|
|
175
190
|
3. **Decorate the route**
|
|
176
191
|
|
|
177
|
-
```
|
|
192
|
+
```ts
|
|
178
193
|
import { Controller, Get } from '@nestjs/common';
|
|
179
194
|
import { RequireToken } from 'nesties';
|
|
180
195
|
|
|
@@ -194,11 +209,16 @@ This approach automatically creates a DTO structure with the properties of `User
|
|
|
194
209
|
|
|
195
210
|
When you need to override the defaults, pass options into `RequireToken`:
|
|
196
211
|
|
|
197
|
-
- `resolver` (default: `{ paramType: 'header', paramName: 'x-server-token' }`): where to read the **client** token from. Accepts any
|
|
198
|
-
- `
|
|
212
|
+
- `resolver` (default: `{ paramType: 'header', paramName: 'x-server-token' }`): where to read the **client** token from. Accepts any **ParamResolver input**:
|
|
213
|
+
- a static header/query descriptor: `{ paramType: 'header' | 'query', paramName: string }`
|
|
214
|
+
- a `ParamResolver` instance
|
|
215
|
+
- `tokenSource` (default: `'SERVER_TOKEN'`): how to read the **server** token. Provide another config key or an async resolver `(req, moduleRef) => Promise<string | undefined>` for dynamic sources.
|
|
199
216
|
- `errorCode` (default: `401`): HTTP status when tokens do not match.
|
|
200
217
|
|
|
201
|
-
```
|
|
218
|
+
```ts
|
|
219
|
+
import { Controller, Get } from '@nestjs/common';
|
|
220
|
+
import { RequireToken } from 'nesties';
|
|
221
|
+
|
|
202
222
|
@Controller('api')
|
|
203
223
|
export class ApiController {
|
|
204
224
|
@Get('protected')
|
|
@@ -213,34 +233,42 @@ export class ApiController {
|
|
|
213
233
|
}
|
|
214
234
|
```
|
|
215
235
|
|
|
216
|
-
Multi-tenant secrets are just another `tokenSource` resolver
|
|
236
|
+
Multi-tenant secrets are just another `tokenSource` resolver. You can compose them using `ParamResolver`:
|
|
217
237
|
|
|
218
|
-
```
|
|
238
|
+
```ts
|
|
219
239
|
import { ConfigService } from '@nestjs/config';
|
|
220
|
-
import {
|
|
240
|
+
import { ParamResolver } from 'nesties';
|
|
221
241
|
|
|
222
|
-
const
|
|
242
|
+
const tenantIdResolver = new ParamResolver({
|
|
243
|
+
paramType: 'header',
|
|
244
|
+
paramName: 'x-tenant-id',
|
|
245
|
+
});
|
|
223
246
|
|
|
224
247
|
@RequireToken({
|
|
225
|
-
resolver:
|
|
226
|
-
tokenSource: async (
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
248
|
+
resolver: { paramType: 'header', paramName: 'x-tenant-token' },
|
|
249
|
+
tokenSource: async (req, moduleRef) => {
|
|
250
|
+
// reuse the same ParamResolver primitives
|
|
251
|
+
const getTenantId = tenantIdResolver.toResolverFunction();
|
|
252
|
+
const tenantId = await getTenantId(req, moduleRef);
|
|
253
|
+
|
|
231
254
|
const config = moduleRef.get(ConfigService);
|
|
232
|
-
return
|
|
255
|
+
return tenantId
|
|
256
|
+
? config.get<string>(`TENANT_${tenantId}_TOKEN`)
|
|
257
|
+
: undefined;
|
|
233
258
|
},
|
|
234
259
|
})
|
|
260
|
+
export class TenantController {
|
|
261
|
+
// ...
|
|
262
|
+
}
|
|
235
263
|
```
|
|
236
264
|
|
|
237
265
|
`TokenGuard` only throws when both values exist and differ, so clearing the config value temporarily disables the guard without a code change.
|
|
238
266
|
|
|
239
267
|
### 6. AbortableModule
|
|
240
268
|
|
|
241
|
-
Use `AbortableModule` when you want long
|
|
269
|
+
Use `AbortableModule` when you want long-running providers to respect the lifetime of the HTTP request. The module exposes a request-scoped `AbortSignal` and wraps existing providers with [`nfkit`](https://www.npmjs.com/package/nfkit)'s `abortable` helper so that work can be canceled automatically when the client disconnects.
|
|
242
270
|
|
|
243
|
-
```
|
|
271
|
+
```ts
|
|
244
272
|
import { AbortableModule, InjectAbortable } from 'nesties';
|
|
245
273
|
|
|
246
274
|
@Module({
|
|
@@ -264,7 +292,7 @@ export class DemoModule {
|
|
|
264
292
|
|
|
265
293
|
#### Injecting `AbortSignal` with `@nestjs/axios`
|
|
266
294
|
|
|
267
|
-
```
|
|
295
|
+
```ts
|
|
268
296
|
import { HttpModule, HttpService } from '@nestjs/axios';
|
|
269
297
|
import {
|
|
270
298
|
AbortableModule,
|
|
@@ -295,13 +323,15 @@ export class WeatherModule {
|
|
|
295
323
|
}
|
|
296
324
|
```
|
|
297
325
|
|
|
298
|
-
The wrapped `HttpService` observes the same abort signal as the request, so in
|
|
326
|
+
The wrapped `HttpService` observes the same abort signal as the request, so in-flight HTTP calls will be canceled as soon as the client disconnects or Nest aborts the request scope.
|
|
299
327
|
|
|
300
328
|
### 7. I18nModule
|
|
301
329
|
|
|
302
330
|
Nesties also ships an opinionated but flexible internationalization module. The typical workflow is to call `createI18n` to obtain `I18nModule` plus the `UseI18n` decorator, register locale lookup middleware (e.g., `I18nLookupMiddleware`), and then return DTOs that contain placeholders like `#{key}`—the interceptor installed by `@UseI18n()` will translate those placeholders automatically before the response leaves the server.
|
|
303
331
|
|
|
304
|
-
|
|
332
|
+
Internally, locale resolution uses the same resolver primitives as `ParamResolver`, so you can read the locale from headers, query parameters, or a custom `(req, moduleRef) => Promise<string | undefined>` function.
|
|
333
|
+
|
|
334
|
+
```ts
|
|
305
335
|
import {
|
|
306
336
|
createI18n,
|
|
307
337
|
I18nService,
|
|
@@ -312,6 +342,7 @@ import {
|
|
|
312
342
|
const { I18nModule, UseI18n } = createI18n({
|
|
313
343
|
locales: ['en-US', 'zh-CN'],
|
|
314
344
|
defaultLocale: 'en-US',
|
|
345
|
+
// resolver: { paramType: 'header', paramName: 'accept-language' } by default
|
|
315
346
|
});
|
|
316
347
|
|
|
317
348
|
@Module({
|
|
@@ -338,12 +369,19 @@ export class GreetingController {
|
|
|
338
369
|
});
|
|
339
370
|
}
|
|
340
371
|
}
|
|
372
|
+
```
|
|
341
373
|
|
|
342
374
|
#### `@PutLocale()` Per-handler Overrides
|
|
343
375
|
|
|
344
|
-
`@PutLocale()` lets you override how the locale is resolved for a specific handler or parameter.
|
|
376
|
+
`@PutLocale()` lets you override how the locale is resolved for a specific handler or parameter. It accepts the same inputs as `ParamResolver`:
|
|
377
|
+
|
|
378
|
+
- a static header/query descriptor: `{ paramType: 'header' | 'query', paramName: string }`
|
|
379
|
+
- a `ParamResolver` instance
|
|
380
|
+
- a dynamic resolver `(req, moduleRef) => Promise<string | undefined>`
|
|
345
381
|
|
|
346
|
-
|
|
382
|
+
When you pass a static descriptor, a `ParamResolver` is created under the hood:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
347
385
|
import { GenericReturnMessageDto, PutLocale } from 'nesties';
|
|
348
386
|
|
|
349
387
|
@Controller('reports')
|
|
@@ -361,11 +399,37 @@ export class ReportController {
|
|
|
361
399
|
}
|
|
362
400
|
```
|
|
363
401
|
|
|
402
|
+
You can also plug in dynamic logic using a `ParamResolver` instance:
|
|
403
|
+
|
|
404
|
+
```ts
|
|
405
|
+
import { ParamResolver, PutLocale } from 'nesties';
|
|
406
|
+
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
|
407
|
+
import { LocaleService } from './locale.service';
|
|
408
|
+
|
|
409
|
+
const dynamicLocaleResolver = new ParamResolver(async (req, ref: ModuleRef) => {
|
|
410
|
+
// example: delegate to a request-scoped LocaleService
|
|
411
|
+
const contextId = ContextIdFactory.getByRequest(req);
|
|
412
|
+
const svc = await ref.resolve(LocaleService, contextId, { strict: false });
|
|
413
|
+
return svc.detectLocale(req);
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
@Controller('dynamic-reports')
|
|
417
|
+
@UseI18n()
|
|
418
|
+
export class DynamicReportController {
|
|
419
|
+
@Get()
|
|
420
|
+
async summary(@PutLocale(dynamicLocaleResolver) locale: string) {
|
|
421
|
+
return new GenericReturnMessageDto(200, 'OK', {
|
|
422
|
+
summary: 'dynamic.report.summary',
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
364
428
|
#### Custom Middleware with TypeORM
|
|
365
429
|
|
|
366
430
|
You can register any number of middlewares that resolve placeholders. The example below queries a TypeORM repository to fetch translations stored in a database and falls back to the next middleware when no record is found.
|
|
367
431
|
|
|
368
|
-
```
|
|
432
|
+
```ts
|
|
369
433
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
|
370
434
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
371
435
|
import {
|
|
@@ -426,28 +490,218 @@ By composing multiple middlewares (dictionaries, database lookups, remote APIs),
|
|
|
426
490
|
- `LocalePipe`/`PutLocale` provide ergonomic access to the resolved locale inside route handlers, and you can override the resolver per parameter when necessary.
|
|
427
491
|
- `I18nService.translate` and `translateString` remain available for advanced manual flows (generating strings outside of interceptor scope, building static assets, etc.).
|
|
428
492
|
|
|
429
|
-
|
|
493
|
+
### 8. ParamResolver
|
|
494
|
+
|
|
495
|
+
`ParamResolver` and `CombinedParamResolver` provide a small, composable abstraction over headers and query parameters. They are used internally by `TokenGuard`, the i18n utilities, and can also be used directly in controllers, pipes, and guards.
|
|
496
|
+
|
|
497
|
+
#### Static header / query resolvers
|
|
498
|
+
|
|
499
|
+
The simplest usage is to read a single header or query parameter:
|
|
500
|
+
|
|
501
|
+
```ts
|
|
502
|
+
import { Controller, Get } from '@nestjs/common';
|
|
503
|
+
import { ParamResolver } from 'nesties';
|
|
504
|
+
|
|
505
|
+
const langHeaderResolver = new ParamResolver({
|
|
506
|
+
paramType: 'header',
|
|
507
|
+
paramName: 'accept-language',
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
const LangHeader = langHeaderResolver.toParamDecorator();
|
|
511
|
+
const ApiLangHeader = langHeaderResolver.toApiPropertyDecorator();
|
|
512
|
+
|
|
513
|
+
@Controller()
|
|
514
|
+
export class LocaleController {
|
|
515
|
+
@Get('header-locale')
|
|
516
|
+
@ApiLangHeader() // documents the header in Swagger
|
|
517
|
+
getLocale(@LangHeader() locale: string | undefined) {
|
|
518
|
+
return { locale };
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
For query parameters:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
const langQueryResolver = new ParamResolver({
|
|
527
|
+
paramType: 'query',
|
|
528
|
+
paramName: 'locale',
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
const LangQuery = langQueryResolver.toParamDecorator();
|
|
532
|
+
|
|
533
|
+
@Controller()
|
|
534
|
+
export class QueryLocaleController {
|
|
535
|
+
@Get('query-locale')
|
|
536
|
+
getLocale(@LangQuery() locale: string | undefined) {
|
|
537
|
+
return { locale };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
Static header resolvers normalize header names to lowercase and perform a best-effort lookup across `req.headers` and common Express-style helpers (`req.get`, `req.header`, `req.getHeader`). Query resolvers first consult `req.query`, then fall back to parsing the current URL when needed.
|
|
543
|
+
|
|
544
|
+
#### Dynamic resolvers with `ModuleRef`
|
|
545
|
+
|
|
546
|
+
When you need more control, `ParamResolver` also accepts a dynamic function `(req, moduleRef) => Promise<string | undefined>`. This is ideal for request-scoped dependencies or multi-step lookups.
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
import { ParamResolver } from 'nesties';
|
|
550
|
+
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
|
551
|
+
import { RequestScopedLocaleService } from './locale.service';
|
|
552
|
+
|
|
553
|
+
const dynamicLocaleResolver = new ParamResolver(
|
|
554
|
+
async (req, ref: ModuleRef) => {
|
|
555
|
+
const contextId = ContextIdFactory.getByRequest(req);
|
|
556
|
+
const svc = await ref.resolve(RequestScopedLocaleService, contextId, {
|
|
557
|
+
strict: false,
|
|
558
|
+
});
|
|
559
|
+
return svc.detectLocale(req);
|
|
560
|
+
},
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
const DynamicLocale = dynamicLocaleResolver.toParamDecorator();
|
|
564
|
+
|
|
565
|
+
@Controller()
|
|
566
|
+
export class DynamicLocaleController {
|
|
567
|
+
@Get('dynamic-locale')
|
|
568
|
+
getLocale(@DynamicLocale() locale: string | undefined) {
|
|
569
|
+
return { locale };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
You can also call dynamic resolvers manually via `toResolverFunction()`:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
import type { Request } from 'express';
|
|
578
|
+
|
|
579
|
+
@Injectable()
|
|
580
|
+
export class LocaleConsumerService {
|
|
581
|
+
constructor(private readonly moduleRef: ModuleRef) {}
|
|
582
|
+
|
|
583
|
+
async getLocale(req: Request) {
|
|
584
|
+
const fn = dynamicLocaleResolver.toResolverFunction();
|
|
585
|
+
return fn(req, this.moduleRef);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
#### Combining multiple resolvers
|
|
591
|
+
|
|
592
|
+
`CombinedParamResolver` lets you compose several resolvers into a single object result and merges their Swagger decorators.
|
|
593
|
+
|
|
594
|
+
```ts
|
|
595
|
+
import {
|
|
596
|
+
CombinedParamResolver,
|
|
597
|
+
ParamResolver,
|
|
598
|
+
TypeFromParamResolver,
|
|
599
|
+
} from 'nesties';
|
|
600
|
+
|
|
601
|
+
const langResolver = new ParamResolver({
|
|
602
|
+
paramType: 'header',
|
|
603
|
+
paramName: 'accept-language',
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const tokenResolver = new ParamResolver({
|
|
607
|
+
paramType: 'header',
|
|
608
|
+
paramName: 'x-access-token',
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
const combinedResolver = new CombinedParamResolver({
|
|
612
|
+
lang: langResolver,
|
|
613
|
+
token: tokenResolver,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
type CombinedResult = TypeFromParamResolver<typeof combinedResolver>;
|
|
617
|
+
// CombinedResult = { lang: string | undefined; token: string | undefined }
|
|
618
|
+
|
|
619
|
+
const CombinedParam = combinedResolver.toParamDecorator();
|
|
620
|
+
const ApiCombined = combinedResolver.toApiPropertyDecorator();
|
|
621
|
+
|
|
622
|
+
@Controller()
|
|
623
|
+
export class CombinedController {
|
|
624
|
+
@Get('combined')
|
|
625
|
+
@ApiCombined()
|
|
626
|
+
inspect(@CombinedParam() params: CombinedResult) {
|
|
627
|
+
return params; // { lang, token }
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
When used as a decorator, the combined resolver:
|
|
633
|
+
|
|
634
|
+
- Executes each underlying resolver in parallel (`Promise.all`)
|
|
635
|
+
- Returns a typed object where each key corresponds to the original resolver
|
|
636
|
+
- Emits merged Swagger metadata for all headers / queries involved
|
|
637
|
+
|
|
638
|
+
#### Request-scoped providers from resolvers
|
|
639
|
+
|
|
640
|
+
Sometimes you want to treat the resolved value itself as an injectable request-scoped provider. You can derive such a provider from any `ParamResolver` or `CombinedParamResolver` using `toRequestScopedProvider()`:
|
|
641
|
+
|
|
642
|
+
```ts
|
|
643
|
+
import { Module } from '@nestjs/common';
|
|
644
|
+
import { ParamResolver } from 'nesties';
|
|
645
|
+
|
|
646
|
+
const userIdResolver = new ParamResolver({
|
|
647
|
+
paramType: 'header',
|
|
648
|
+
paramName: 'x-user-id',
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const userIdProviderMeta = userIdResolver.toRequestScopedProvider();
|
|
652
|
+
|
|
653
|
+
@Module({
|
|
654
|
+
providers: [userIdProviderMeta.provider],
|
|
655
|
+
})
|
|
656
|
+
export class UserModule {
|
|
657
|
+
constructor(
|
|
658
|
+
@userIdProviderMeta.inject()
|
|
659
|
+
private readonly userId: string | undefined,
|
|
660
|
+
) {}
|
|
661
|
+
|
|
662
|
+
// userId is now resolved once per request and injectable anywhere in scope
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
This pattern is useful when you want to reuse the same resolver logic in guards, interceptors, and services without manually passing around the `Request` object.
|
|
667
|
+
|
|
668
|
+
#### Helper functions and deprecations
|
|
669
|
+
|
|
670
|
+
- `getParamResolver(input: ParamResolverInput)`
|
|
671
|
+
Normalizes either a static descriptor or a `ParamResolver` instance into a resolver instance.
|
|
672
|
+
|
|
673
|
+
- `createResolver(input: ParamResolverInput)`
|
|
674
|
+
Returns `ParamResolver.toResolverFunction()` for quick inline usage. This is kept for backwards compatibility and may be deprecated in favor of constructing `new ParamResolver(input)` directly.
|
|
675
|
+
|
|
676
|
+
- `ApiFromResolver(input: ParamResolverInput, extras?: ApiHeaderOptions | ApiQueryOptions)`
|
|
677
|
+
Convenience helper to generate Swagger decorators from resolver inputs.
|
|
678
|
+
|
|
679
|
+
### 9. DTO Classes
|
|
430
680
|
|
|
431
681
|
- **BlankReturnMessageDto**: A basic DTO for standardized API responses.
|
|
432
682
|
- **BlankPaginatedReturnMessageDto**: A DTO for paginated API responses.
|
|
433
683
|
- **GenericReturnMessageDto**: A generic DTO for returning data of any type.
|
|
434
684
|
- **StringReturnMessageDto**: A simple DTO for string responses.
|
|
435
685
|
|
|
436
|
-
```
|
|
686
|
+
```ts
|
|
437
687
|
import { StringReturnMessageDto } from 'nesties';
|
|
438
688
|
|
|
439
|
-
const response = new StringReturnMessageDto(
|
|
689
|
+
const response = new StringReturnMessageDto(
|
|
690
|
+
200,
|
|
691
|
+
'Success',
|
|
692
|
+
'This is a string response',
|
|
693
|
+
);
|
|
440
694
|
```
|
|
441
695
|
|
|
442
696
|
## Configuration
|
|
443
697
|
|
|
444
698
|
The `TokenGuard` class uses the `ConfigService` from `@nestjs/config` to access configuration values, such as the `SERVER_TOKEN`. Make sure you have `@nestjs/config` installed and configured in your Nest.js project.
|
|
445
699
|
|
|
446
|
-
```
|
|
700
|
+
```ts
|
|
447
701
|
import { ConfigModule } from '@nestjs/config';
|
|
448
702
|
|
|
449
703
|
@Module({
|
|
450
|
-
imports: [ConfigModule.forRoot()],
|
|
704
|
+
imports: [ConfigModule.forRoot()],
|
|
451
705
|
})
|
|
452
706
|
export class AppModule {}
|
|
453
707
|
```
|