@moostjs/event-http 0.5.33 → 0.6.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.
- package/README.md +67 -3
- package/dist/index.cjs +261 -83
- package/dist/index.d.ts +123 -22
- package/dist/index.mjs +248 -86
- package/package.json +40 -34
- package/scripts/setup-skills.js +76 -0
- package/skills/moostjs-event-http/SKILL.md +32 -0
- package/skills/moostjs-event-http/auth.md +275 -0
- package/skills/moostjs-event-http/core.md +193 -0
- package/skills/moostjs-event-http/request.md +230 -0
- package/skills/moostjs-event-http/response.md +287 -0
- package/skills/moostjs-event-http/routing.md +210 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Request data — @moostjs/event-http
|
|
2
|
+
|
|
3
|
+
> Extracting data from incoming HTTP requests using resolver decorators.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
Moost provides **resolver decorators** that extract values from the incoming request and inject them as handler method parameters. Each decorator wraps a `@Resolve()` call that invokes the appropriate `@wooksjs/event-http` composable. All resolver decorators can also be used as **property decorators** on `FOR_EVENT`-scoped controllers.
|
|
8
|
+
|
|
9
|
+
## API Reference
|
|
10
|
+
|
|
11
|
+
### `@Query(name?)`
|
|
12
|
+
|
|
13
|
+
Extract query parameters. With a name, returns a single value; without, returns all as an object.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Get, Query } from '@moostjs/event-http'
|
|
17
|
+
|
|
18
|
+
@Get('search')
|
|
19
|
+
search(
|
|
20
|
+
@Query('q') query: string, // single param
|
|
21
|
+
@Query() params: Record<string, string>, // all params
|
|
22
|
+
) {}
|
|
23
|
+
// GET /search?q=moost&limit=10
|
|
24
|
+
// query = 'moost', params = { q: 'moost', limit: '10' }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- Returns `undefined` if the parameter is missing
|
|
28
|
+
- `@Query()` returns `undefined` (not `{}`) when there are no query params at all
|
|
29
|
+
- Sets metadata: `paramSource: 'QUERY_ITEM'` (named) or `'QUERY'` (all)
|
|
30
|
+
|
|
31
|
+
### `@Header(name)`
|
|
32
|
+
|
|
33
|
+
Extract a request header value.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Get, Header } from '@moostjs/event-http'
|
|
37
|
+
|
|
38
|
+
@Get('test')
|
|
39
|
+
test(@Header('content-type') contentType: string) {}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Header names are case-insensitive.
|
|
43
|
+
|
|
44
|
+
### `@Cookie(name)`
|
|
45
|
+
|
|
46
|
+
Extract a request cookie value.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Get, Cookie } from '@moostjs/event-http'
|
|
50
|
+
|
|
51
|
+
@Get('profile')
|
|
52
|
+
profile(@Cookie('session') session: string) {}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### `@Body()`
|
|
56
|
+
|
|
57
|
+
Parse and return the request body. Automatically detects JSON, form-encoded, and text content types.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { Post, Body } from '@moostjs/event-http'
|
|
61
|
+
|
|
62
|
+
@Post('users')
|
|
63
|
+
create(@Body() data: { name: string, email: string }) {}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- Sets metadata: `paramSource: 'BODY'`
|
|
67
|
+
- Uses `@wooksjs/http-body` for parsing
|
|
68
|
+
|
|
69
|
+
### `@RawBody()`
|
|
70
|
+
|
|
71
|
+
Get the raw request body as a `Buffer`.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { Post, RawBody } from '@moostjs/event-http'
|
|
75
|
+
|
|
76
|
+
@Post('upload')
|
|
77
|
+
upload(@RawBody() raw: Buffer) {}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### `@Authorization(field)`
|
|
81
|
+
|
|
82
|
+
Extract parts of the `Authorization` header.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { Get, Authorization } from '@moostjs/event-http'
|
|
86
|
+
|
|
87
|
+
@Get('profile')
|
|
88
|
+
profile(
|
|
89
|
+
@Authorization('bearer') token: string, // Bearer token (no prefix)
|
|
90
|
+
@Authorization('type') authType: string, // "Bearer", "Basic", etc.
|
|
91
|
+
@Authorization('username') user: string, // from Basic auth
|
|
92
|
+
@Authorization('password') pass: string, // from Basic auth
|
|
93
|
+
@Authorization('raw') credentials: string, // raw credentials string
|
|
94
|
+
) {}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Valid fields: `'username'`, `'password'`, `'bearer'`, `'raw'`, `'type'`
|
|
98
|
+
|
|
99
|
+
### `@Url()`
|
|
100
|
+
|
|
101
|
+
Get the requested URL string.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { Get, Url } from '@moostjs/event-http'
|
|
105
|
+
|
|
106
|
+
@Get('info')
|
|
107
|
+
info(@Url() url: string) {}
|
|
108
|
+
// url = '/info?page=1'
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### `@Method()`
|
|
112
|
+
|
|
113
|
+
Get the HTTP method string.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { All, Method } from '@moostjs/event-http'
|
|
117
|
+
|
|
118
|
+
@All('proxy')
|
|
119
|
+
proxy(@Method() method: string) {}
|
|
120
|
+
// method = 'GET', 'POST', etc.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `@Req()`
|
|
124
|
+
|
|
125
|
+
Get the raw Node.js `IncomingMessage`.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { Get, Req } from '@moostjs/event-http'
|
|
129
|
+
import type { IncomingMessage } from 'http'
|
|
130
|
+
|
|
131
|
+
@Get('raw')
|
|
132
|
+
raw(@Req() request: IncomingMessage) {
|
|
133
|
+
return { httpVersion: request.httpVersion }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `@Res(opts?)`
|
|
138
|
+
|
|
139
|
+
Get the raw Node.js `ServerResponse`. When used, the framework does **not** process the return value.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { Get, Res } from '@moostjs/event-http'
|
|
143
|
+
import type { ServerResponse } from 'http'
|
|
144
|
+
|
|
145
|
+
@Get('raw')
|
|
146
|
+
raw(@Res() res: ServerResponse) {
|
|
147
|
+
res.writeHead(200, { 'content-type': 'text/plain' })
|
|
148
|
+
res.end('Manual response')
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Pass `{ passthrough: true }` to get the raw response but still let the framework handle the return value:
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
@Get('hybrid')
|
|
156
|
+
hybrid(@Res({ passthrough: true }) res: ServerResponse) {
|
|
157
|
+
res.setHeader('x-custom', 'value')
|
|
158
|
+
return { data: 'processed by framework' }
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### `@ReqId()`
|
|
163
|
+
|
|
164
|
+
Get the unique request UUID.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { Get, ReqId } from '@moostjs/event-http'
|
|
168
|
+
|
|
169
|
+
@Get('test')
|
|
170
|
+
test(@ReqId() requestId: string) {}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `@Ip(opts?)`
|
|
174
|
+
|
|
175
|
+
Get the client IP address.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { Get, Ip } from '@moostjs/event-http'
|
|
179
|
+
|
|
180
|
+
@Get('client')
|
|
181
|
+
client(
|
|
182
|
+
@Ip() ip: string, // direct client IP
|
|
183
|
+
@Ip({ trustProxy: true }) realIp: string, // considers x-forwarded-for
|
|
184
|
+
) {}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `@IpList()`
|
|
188
|
+
|
|
189
|
+
Get the full IP address chain.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { Get, IpList } from '@moostjs/event-http'
|
|
193
|
+
|
|
194
|
+
@Get('client')
|
|
195
|
+
client(@IpList() allIps: string[]) {}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Decorator summary table
|
|
199
|
+
|
|
200
|
+
| Decorator | Returns | Import |
|
|
201
|
+
|-----------|---------|--------|
|
|
202
|
+
| `@Param(name)` | Route parameter | `moost` |
|
|
203
|
+
| `@Params()` | All route params | `moost` |
|
|
204
|
+
| `@Query(name?)` | Query param(s) | `@moostjs/event-http` |
|
|
205
|
+
| `@Header(name)` | Header value | `@moostjs/event-http` |
|
|
206
|
+
| `@Cookie(name)` | Cookie value | `@moostjs/event-http` |
|
|
207
|
+
| `@Body()` | Parsed body | `@moostjs/event-http` |
|
|
208
|
+
| `@RawBody()` | Raw Buffer | `@moostjs/event-http` |
|
|
209
|
+
| `@Authorization(field)` | Auth field | `@moostjs/event-http` |
|
|
210
|
+
| `@Url()` | URL string | `@moostjs/event-http` |
|
|
211
|
+
| `@Method()` | HTTP method | `@moostjs/event-http` |
|
|
212
|
+
| `@ReqId()` | Request UUID | `@moostjs/event-http` |
|
|
213
|
+
| `@Ip(opts?)` | Client IP | `@moostjs/event-http` |
|
|
214
|
+
| `@IpList()` | IP chain | `@moostjs/event-http` |
|
|
215
|
+
| `@Req()` | IncomingMessage | `@moostjs/event-http` |
|
|
216
|
+
| `@Res(opts?)` | ServerResponse | `@moostjs/event-http` |
|
|
217
|
+
|
|
218
|
+
## Best Practices
|
|
219
|
+
|
|
220
|
+
- Use `@Authorization()` for quick header parsing; use `@Authenticate()` guards for production auth
|
|
221
|
+
- Prefer `@Body()` over `@RawBody()` unless you need the raw bytes
|
|
222
|
+
- Query values are always strings — use pipes to transform to numbers or other types
|
|
223
|
+
- Use `@Ip({ trustProxy: true })` behind reverse proxies to get the real client IP
|
|
224
|
+
|
|
225
|
+
## Gotchas
|
|
226
|
+
|
|
227
|
+
- `@Query('name')` returns `undefined` when the parameter is missing (not `null` or empty string)
|
|
228
|
+
- `@Query()` (all params) returns `undefined` when there are no query params, not an empty object
|
|
229
|
+
- `@Res()` without `passthrough` takes over the response — the handler's return value is ignored
|
|
230
|
+
- `@Param` and `@Params` are imported from `moost`, not from `@moostjs/event-http`
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# Response control — @moostjs/event-http
|
|
2
|
+
|
|
3
|
+
> Setting status codes, headers, cookies, handling errors, and controlling HTTP responses.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
Moost provides two styles for response control:
|
|
8
|
+
|
|
9
|
+
1. **Static decorators** (`@SetStatus`, `@SetHeader`, `@SetCookie`) — applied via interceptors at `AFTER_ALL` priority, declarative and fixed
|
|
10
|
+
2. **Dynamic hooks** (`@StatusHook`, `@HeaderHook`, `@CookieHook`, `@CookieAttrsHook`) — Proxy-based reactive bindings, set values programmatically at runtime
|
|
11
|
+
|
|
12
|
+
Both work as method decorators. Hook decorators also work as parameter decorators and property decorators (on `FOR_EVENT`-scoped controllers).
|
|
13
|
+
|
|
14
|
+
## API Reference
|
|
15
|
+
|
|
16
|
+
### `@SetStatus(code, opts?)`
|
|
17
|
+
|
|
18
|
+
Set the response status code. Runs as an `after` interceptor.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Post, SetStatus } from '@moostjs/event-http'
|
|
22
|
+
|
|
23
|
+
@Post('users')
|
|
24
|
+
@SetStatus(201)
|
|
25
|
+
create() { return { created: true } }
|
|
26
|
+
|
|
27
|
+
// Force override even if status was already set
|
|
28
|
+
@SetStatus(200, { force: true })
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### `@StatusHook()`
|
|
32
|
+
|
|
33
|
+
Dynamic status code control via a `{ value: number }` proxy object.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Get, StatusHook } from '@moostjs/event-http'
|
|
37
|
+
import type { TStatusHook } from '@moostjs/event-http'
|
|
38
|
+
|
|
39
|
+
@Get('process')
|
|
40
|
+
process(@StatusHook() status: TStatusHook) {
|
|
41
|
+
if (someCondition) {
|
|
42
|
+
status.value = 202
|
|
43
|
+
return { status: 'processing' }
|
|
44
|
+
}
|
|
45
|
+
return { status: 'done' } // default 200
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Works as a property decorator too:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
@Injectable('FOR_EVENT')
|
|
53
|
+
@Controller()
|
|
54
|
+
class MyController {
|
|
55
|
+
@StatusHook()
|
|
56
|
+
status = 200 // initial value
|
|
57
|
+
|
|
58
|
+
@Get('test')
|
|
59
|
+
test() {
|
|
60
|
+
this.status = 201 // reactive — sets the response status
|
|
61
|
+
return 'created'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `@SetHeader(name, value, opts?)`
|
|
67
|
+
|
|
68
|
+
Set a response header. Runs as an `after` interceptor (and optionally on error).
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { Get, SetHeader } from '@moostjs/event-http'
|
|
72
|
+
|
|
73
|
+
@Get('test')
|
|
74
|
+
@SetHeader('x-powered-by', 'moost')
|
|
75
|
+
@SetHeader('cache-control', 'no-store')
|
|
76
|
+
test() { return 'ok' }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
|
|
81
|
+
| Option | Type | Description |
|
|
82
|
+
|--------|------|-------------|
|
|
83
|
+
| `force` | `boolean` | Override header even if already set |
|
|
84
|
+
| `status` | `number` | Only set when response has this status |
|
|
85
|
+
| `when` | `'always' \| 'error' \| 'ok'` | When to apply (default: success only) |
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
@SetHeader('content-type', 'text/plain', { status: 400 })
|
|
89
|
+
@SetHeader('x-request-id', 'abc', { when: 'always' })
|
|
90
|
+
@SetHeader('x-error', 'true', { when: 'error' })
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### `@HeaderHook(name)`
|
|
94
|
+
|
|
95
|
+
Dynamic header control via a `{ value: string | string[] | undefined }` proxy.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { Get, HeaderHook } from '@moostjs/event-http'
|
|
99
|
+
import type { THeaderHook } from '@moostjs/event-http'
|
|
100
|
+
|
|
101
|
+
@Get('test')
|
|
102
|
+
test(@HeaderHook('x-custom') header: THeaderHook) {
|
|
103
|
+
header.value = `generated-${Date.now()}`
|
|
104
|
+
return 'ok'
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `@SetCookie(name, value, attrs?)`
|
|
109
|
+
|
|
110
|
+
Set a response cookie. Only sets if the cookie hasn't already been set in the response.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { Get, SetCookie } from '@moostjs/event-http'
|
|
114
|
+
|
|
115
|
+
@Get('login')
|
|
116
|
+
@SetCookie('session', 'abc123', { maxAge: '1h', httpOnly: true })
|
|
117
|
+
login() { return { ok: true } }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Cookie attributes (`TCookieAttributesInput`): `maxAge`, `expires`, `domain`, `path`, `secure`, `httpOnly`, `sameSite`, etc.
|
|
121
|
+
|
|
122
|
+
### `@CookieHook(name)`
|
|
123
|
+
|
|
124
|
+
Dynamic cookie value control.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { Post, CookieHook } from '@moostjs/event-http'
|
|
128
|
+
import type { TCookieHook } from '@moostjs/event-http'
|
|
129
|
+
|
|
130
|
+
@Post('login')
|
|
131
|
+
login(@CookieHook('session') cookie: TCookieHook) {
|
|
132
|
+
cookie.value = generateToken()
|
|
133
|
+
return { ok: true }
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `@CookieAttrsHook(name)`
|
|
138
|
+
|
|
139
|
+
Dynamic cookie attributes control.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { Post, CookieAttrsHook } from '@moostjs/event-http'
|
|
143
|
+
import type { TCookieAttributes } from '@moostjs/event-http'
|
|
144
|
+
|
|
145
|
+
@Post('login')
|
|
146
|
+
login(@CookieAttrsHook('session') attrs: { value: TCookieAttributes }) {
|
|
147
|
+
attrs.value = { maxAge: '1h', httpOnly: true, secure: true }
|
|
148
|
+
return { ok: true }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Body limits
|
|
153
|
+
|
|
154
|
+
Interceptor-based decorators for controlling request body parsing limits.
|
|
155
|
+
|
|
156
|
+
### `@BodySizeLimit(n)`
|
|
157
|
+
|
|
158
|
+
Limit the maximum inflated (decompressed) body size in bytes. Default: 10 MB.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
import { Post, BodySizeLimit } from '@moostjs/event-http'
|
|
162
|
+
|
|
163
|
+
@Post('upload')
|
|
164
|
+
@BodySizeLimit(50 * 1024 * 1024) // 50 MB
|
|
165
|
+
upload() {}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `@CompressedBodySizeLimit(n)`
|
|
169
|
+
|
|
170
|
+
Limit the maximum compressed body size in bytes. Default: 1 MB.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
@Post('upload')
|
|
174
|
+
@CompressedBodySizeLimit(5 * 1024 * 1024) // 5 MB
|
|
175
|
+
upload() {}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `@BodyReadTimeoutMs(n)`
|
|
179
|
+
|
|
180
|
+
Set the timeout for reading the request body in milliseconds. Default: 10 s.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
@Post('upload')
|
|
184
|
+
@BodyReadTimeoutMs(30000) // 30 seconds
|
|
185
|
+
upload() {}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Global limit interceptors
|
|
189
|
+
|
|
190
|
+
For applying limits globally:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { globalBodySizeLimit, globalCompressedBodySizeLimit, globalBodyReadTimeoutMs } from '@moostjs/event-http'
|
|
194
|
+
|
|
195
|
+
const app = new Moost()
|
|
196
|
+
app.applyGlobalInterceptors(
|
|
197
|
+
globalBodySizeLimit(20 * 1024 * 1024),
|
|
198
|
+
globalCompressedBodySizeLimit(2 * 1024 * 1024),
|
|
199
|
+
globalBodyReadTimeoutMs(15000),
|
|
200
|
+
)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Error handling
|
|
204
|
+
|
|
205
|
+
### `HttpError`
|
|
206
|
+
|
|
207
|
+
Throw to produce an HTTP error response:
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { HttpError } from '@moostjs/event-http'
|
|
211
|
+
|
|
212
|
+
throw new HttpError(404, 'Not Found')
|
|
213
|
+
throw new HttpError(403, 'Access denied')
|
|
214
|
+
throw new HttpError(422, {
|
|
215
|
+
message: 'Validation failed',
|
|
216
|
+
statusCode: 422,
|
|
217
|
+
errors: [
|
|
218
|
+
{ field: 'email', message: 'Invalid email format' },
|
|
219
|
+
],
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Uncaught exceptions become HTTP 500 responses. The response format (JSON or HTML) adapts based on the `Accept` header.
|
|
224
|
+
|
|
225
|
+
## Common Patterns
|
|
226
|
+
|
|
227
|
+
### Pattern: CRUD with proper status codes
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
@Controller('items')
|
|
231
|
+
class ItemController {
|
|
232
|
+
@Get('')
|
|
233
|
+
list() { return items }
|
|
234
|
+
|
|
235
|
+
@Post('')
|
|
236
|
+
@SetStatus(201)
|
|
237
|
+
create(@Body() data: CreateItemDto) { return createItem(data) }
|
|
238
|
+
|
|
239
|
+
@Put(':id')
|
|
240
|
+
update(@Param('id') id: string, @Body() data: UpdateItemDto) {
|
|
241
|
+
return updateItem(id, data)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@Delete(':id')
|
|
245
|
+
@SetStatus(204)
|
|
246
|
+
remove(@Param('id') id: string) { deleteItem(id) }
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Pattern: Conditional response headers
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
@Get('data')
|
|
254
|
+
@SetHeader('x-cache', 'miss', { when: 'always' })
|
|
255
|
+
getData(@HeaderHook('x-cache') cache: THeaderHook) {
|
|
256
|
+
const cached = getFromCache()
|
|
257
|
+
if (cached) {
|
|
258
|
+
cache.value = 'hit'
|
|
259
|
+
return cached
|
|
260
|
+
}
|
|
261
|
+
return fetchFreshData()
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Types
|
|
266
|
+
|
|
267
|
+
```ts
|
|
268
|
+
export interface TStatusHook { value: number }
|
|
269
|
+
export interface THeaderHook { value: string | string[] | undefined }
|
|
270
|
+
export interface TCookieHook { value: string; attrs?: TCookieAttributes }
|
|
271
|
+
export type TCookieAttributes = Partial<TCookieAttributesRequired>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Best Practices
|
|
275
|
+
|
|
276
|
+
- Use `@SetStatus` for fixed status codes (201 for creation, 204 for deletion)
|
|
277
|
+
- Use `@StatusHook` when the status depends on runtime logic
|
|
278
|
+
- Use `@SetHeader` for static headers, `@HeaderHook` for dynamic ones
|
|
279
|
+
- Apply body limits on upload endpoints to prevent abuse
|
|
280
|
+
- Use global limit interceptors for application-wide defaults
|
|
281
|
+
|
|
282
|
+
## Gotchas
|
|
283
|
+
|
|
284
|
+
- `@SetStatus` won't override a status already set (e.g., by an error) unless `{ force: true }` is passed
|
|
285
|
+
- `@SetCookie` won't overwrite a cookie already set in the response — this prevents accidental overwrites
|
|
286
|
+
- `@SetHeader` defaults to success-only (`after` interceptor); use `{ when: 'always' }` to include error responses
|
|
287
|
+
- Hook decorators use Proxy objects — the `.value` property is reactive, not a plain field
|