@moostjs/event-http 0.5.32 → 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.
@@ -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