@wooksjs/event-http 0.6.6 → 0.7.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.
@@ -1,220 +1,204 @@
1
- # Request Utilities — @wooksjs/event-http
1
+ # Request composables — @wooksjs/event-http
2
2
 
3
- > Covers composables for reading incoming request data: headers, cookies, query params, authorization, IP address, and Accept header.
3
+ > Reading request data: headers, cookies, query params, body, authorization, IP address.
4
4
 
5
- ## `useRequest()`
5
+ ## Concepts
6
6
 
7
- Primary composable for accessing the raw incoming HTTP request.
7
+ All request data is accessed through composables — functions that resolve context via `AsyncLocalStorage`. They take no arguments (optionally accept `ctx` for performance). Data is parsed lazily on first access and cached for the request lifetime.
8
8
 
9
- ```ts
10
- import { useRequest } from '@wooksjs/event-http'
9
+ All composables are importable from `@wooksjs/event-http`.
11
10
 
12
- app.get('/info', () => {
13
- const { method, url, headers, rawBody, reqId, getIp } = useRequest()
11
+ ## API Reference
14
12
 
15
- return {
16
- method, // 'GET'
17
- url, // '/info?page=1'
18
- host: headers.host,
19
- ip: getIp(),
20
- requestId: reqId(),
21
- }
22
- })
23
- ```
13
+ ### `useRequest(ctx?)`
14
+
15
+ The primary request composable. Returns method, URL, headers, raw body, IP, and request limit controls.
24
16
 
25
- ### Properties & methods
17
+ ```ts
18
+ import { useRequest } from '@wooksjs/event-http'
26
19
 
27
- | Name | Type | Description |
28
- |------|------|-------------|
29
- | `rawRequest` | `IncomingMessage` | The raw Node.js request object |
30
- | `method` | `string` | HTTP method (GET, POST, etc.) |
31
- | `url` | `string` | Raw request URL including query string |
32
- | `headers` | `IncomingHttpHeaders` | Request headers object |
33
- | `rawBody()` | `() => Promise<Buffer>` | Lazily reads and decompresses the request body |
34
- | `reqId()` | `() => string` | Lazily generates a UUID for this request |
35
- | `getIp(opts?)` | `(opts?) => string` | Returns client IP (supports `trustProxy`) |
36
- | `getIpList()` | `() => { remoteIp, forwarded }` | Returns all known IPs |
37
- | `isCompressed()` | `() => boolean` | Whether the body is compressed |
20
+ const { method, url, headers, rawBody, getIp, reqId } = useRequest()
21
+ ```
38
22
 
39
- ### Body size limits
23
+ **Returned properties:**
40
24
 
41
- | Limit | Default | Description |
42
- |-------|---------|-------------|
43
- | `maxCompressed` | 1 MB (1 048 576) | Max compressed body size in bytes |
44
- | `maxInflated` | 10 MB (10 485 760) | Max decompressed body size in bytes |
45
- | `maxRatio` | 100 | Max compression ratio (zip-bomb protection) |
46
- | `readTimeoutMs` | 10 000 | Body read timeout in milliseconds |
25
+ | Property | Type | Description |
26
+ | ---------------- | -------------------------------------------- | -------------------------------------------------- |
27
+ | `rawRequest` | `IncomingMessage` | Node.js raw request object |
28
+ | `url` | `string` | Request URL |
29
+ | `method` | `string` | HTTP method |
30
+ | `headers` | `IncomingHttpHeaders` | Request headers |
31
+ | `rawBody` | `() => Promise<Buffer>` | Lazy — reads and decompresses request body on call |
32
+ | `reqId` | `() => string` | Lazy UUID per request |
33
+ | `getIp(opts?)` | `(opts?: { trustProxy: boolean }) => string` | Client IP (with optional proxy trust) |
34
+ | `getIpList()` | `() => { remoteIp: string; forwarded: string[] }` | All IPs (remote + X-Forwarded-For) |
35
+ | `isCompressed()` | `() => boolean` | Whether the request body is compressed |
47
36
 
48
- Limits can be set **app-wide** via `createHttpApp({ requestLimits: { ... } })` (see [core.md](core.md)) or **per-request** via the setters below (which override app defaults):
37
+ **Request limits (per-request override):**
49
38
 
50
39
  ```ts
51
- const {
52
- getMaxCompressed, setMaxCompressed, // default: 1 MB
53
- getMaxInflated, setMaxInflated, // default: 10 MB
54
- getMaxRatio, setMaxRatio, // default: 100× (zip-bomb protection)
55
- getReadTimeoutMs, setReadTimeoutMs, // default: 10s
56
- } = useRequest()
57
-
58
- // Override per-route for file uploads
59
- setMaxCompressed(50 * 1024 * 1024) // 50 MB
60
- setMaxInflated(100 * 1024 * 1024) // 100 MB
61
- setMaxRatio(200) // allow 200× compression ratio
40
+ const { setMaxCompressed, setMaxInflated, setMaxRatio, setReadTimeoutMs } = useRequest()
41
+ setMaxCompressed(5 * 1024 * 1024) // 5 MB
42
+ setReadTimeoutMs(30_000) // 30 seconds
62
43
  ```
63
44
 
64
- ### IP address with proxy support
45
+ Default limits: `maxCompressed: 1MB`, `maxInflated: 10MB`, `maxRatio: 100`, `readTimeoutMs: 10s`.
65
46
 
66
- ```ts
67
- const { getIp } = useRequest()
47
+ ### `useHeaders(ctx?): IncomingHttpHeaders`
68
48
 
69
- // Direct connection IP
70
- const ip = getIp()
49
+ Returns request headers directly. Shorthand for `useRequest().headers`.
71
50
 
72
- // Trust X-Forwarded-For header (behind reverse proxy)
73
- const clientIp = getIp({ trustProxy: true })
51
+ ```ts
52
+ import { useHeaders } from '@wooksjs/event-http'
53
+
54
+ const { host, authorization, 'content-type': contentType } = useHeaders()
74
55
  ```
75
56
 
76
- ## `useHeaders()`
57
+ ### `useCookies(ctx?)`
77
58
 
78
- Returns incoming request headers directly:
59
+ Parses incoming request cookies lazily (per cookie name, via `cachedBy`).
79
60
 
80
61
  ```ts
81
- import { useHeaders } from '@wooksjs/event-http'
62
+ import { useCookies } from '@wooksjs/event-http'
82
63
 
83
- app.get('/check', () => {
84
- const { host, authorization, 'content-type': contentType } = useHeaders()
85
- return { host, hasAuth: !!authorization }
86
- })
64
+ const { getCookie, rawCookies } = useCookies()
65
+ const session = getCookie('session_id') // parsed + cached
66
+ const theme = getCookie('theme') // parsed + cached (different key)
67
+ const raw = rawCookies // raw Cookie header string
87
68
  ```
88
69
 
89
- Returns a standard `IncomingHttpHeaders` object (same as `req.headers`).
70
+ ### `useSearchParams(ctx?)`
90
71
 
91
- ## `useSearchParams()`
92
-
93
- Access URL query parameters (lazy-parsed, cached):
72
+ Provides access to URL query parameters.
94
73
 
95
74
  ```ts
96
75
  import { useSearchParams } from '@wooksjs/event-http'
97
76
 
98
- app.get('/search', () => {
99
- const { urlSearchParams, jsonSearchParams, rawSearchParams } = useSearchParams()
100
-
101
- // URLSearchParams-like API
102
- const page = urlSearchParams().get('page') // '1'
103
- const tags = urlSearchParams().getAll('tag') // ['a', 'b']
77
+ const { urlSearchParams, jsonSearchParams, rawSearchParams } = useSearchParams()
104
78
 
105
- // As a plain object (handles repeated keys as arrays)
106
- const allParams = jsonSearchParams() // { page: '1', tag: ['a', 'b'] }
79
+ // URLSearchParams API
80
+ const page = urlSearchParams().get('page')
81
+ const tags = urlSearchParams().getAll('tag')
107
82
 
108
- // Raw query string
109
- const raw = rawSearchParams() // '?page=1&tag=a&tag=b'
83
+ // As a plain object
84
+ const query = jsonSearchParams() // { page: '1', tag: ['a', 'b'] }
110
85
 
111
- return { page, tags, allParams }
112
- })
86
+ // Raw query string
87
+ const raw = rawSearchParams() // '?page=1&tag=a&tag=b'
113
88
  ```
114
89
 
115
- ## `useCookies()`
90
+ ### `useAuthorization(ctx?)`
116
91
 
117
- Parse incoming request cookies (lazy per-cookie parsing):
92
+ Parses the Authorization header (supports Basic and Bearer).
118
93
 
119
94
  ```ts
120
- import { useCookies } from '@wooksjs/event-http'
95
+ import { useAuthorization } from '@wooksjs/event-http'
121
96
 
122
- app.get('/dashboard', () => {
123
- const { getCookie, rawCookies } = useCookies()
97
+ const { authorization, authType, authRawCredentials, authIs, basicCredentials } = useAuthorization()
124
98
 
125
- const session = getCookie('session_id') // 'abc123' or null
126
- const theme = getCookie('theme') // 'dark' or null
99
+ if (authIs('bearer')) {
100
+ const token = authRawCredentials() // the raw token string
101
+ }
127
102
 
128
- return { session, theme }
129
- })
103
+ if (authIs('basic')) {
104
+ const { username, password } = basicCredentials()!
105
+ }
130
106
  ```
131
107
 
132
- Each cookie is parsed individually on first access and cached. If you never call `getCookie('theme')`, the `theme` cookie is never parsed.
133
-
134
- ## `useAuthorization()`
108
+ **Returned properties:**
135
109
 
136
- Parse the `Authorization` header (lazy, cached):
110
+ | Property | Type | Description |
111
+ | ---------------------- | -------------------------------- | -------------------------------------------------------------- |
112
+ | `authorization` | `string \| undefined` | Raw Authorization header value |
113
+ | `authType()` | `string \| null` | Auth scheme: `'Basic'`, `'Bearer'`, etc. |
114
+ | `authRawCredentials()` | `string \| null` | Everything after the scheme |
115
+ | `authIs(type)` | `boolean` | Check auth scheme: `'basic'`, `'bearer'`, or any custom scheme |
116
+ | `basicCredentials()` | `{ username, password } \| null` | Decoded Basic credentials |
137
117
 
138
- ```ts
139
- import { useAuthorization } from '@wooksjs/event-http'
118
+ ### `useAccept(ctx?)`
140
119
 
141
- app.get('/protected', () => {
142
- const { isBearer, isBasic, authType, authRawCredentials, basicCredentials } = useAuthorization()
120
+ Checks the request's `Accept` header for MIME type support. Uses short names (`'json'`, `'html'`, `'xml'`, `'text'`) or full MIME types.
143
121
 
144
- if (isBearer()) {
145
- const token = authRawCredentials() // 'eyJhbGciOi...'
146
- // validate JWT
147
- }
122
+ ```ts
123
+ import { useAccept } from '@wooksjs/event-http'
148
124
 
149
- if (isBasic()) {
150
- const { username, password } = basicCredentials()!
151
- // validate credentials
152
- }
125
+ const { accept, accepts } = useAccept()
153
126
 
154
- return { authType: authType() } // 'Bearer', 'Basic', etc.
155
- })
127
+ if (accepts('json')) {
128
+ /* ... */
129
+ }
130
+ if (accepts('image/png')) {
131
+ /* ... */
132
+ }
156
133
  ```
157
134
 
158
- ### Methods
135
+ ### `useRouteParams<T>(ctx?)`
159
136
 
160
- | Name | Returns | Description |
161
- |------|---------|-------------|
162
- | `authorization` | `string \| undefined` | Raw header value |
163
- | `authType()` | `string \| null` | Auth scheme (Bearer, Basic, etc.) |
164
- | `authRawCredentials()` | `string \| null` | Everything after the scheme |
165
- | `isBearer()` | `boolean` | True if Bearer auth |
166
- | `isBasic()` | `boolean` | True if Basic auth |
167
- | `basicCredentials()` | `{ username, password } \| null` | Decoded Basic credentials |
137
+ Route parameters from the URL. Re-exported from `@wooksjs/event-core`.
168
138
 
169
- ## `useAccept()`
139
+ ```ts
140
+ import { useRouteParams } from '@wooksjs/event-http'
170
141
 
171
- Check the `Accept` header for content negotiation:
142
+ // Given route: /users/:id/posts/:postId
143
+ const { params, get } = useRouteParams<{ id: string; postId: string }>()
144
+ params.id // '42'
145
+ get('postId') // '7'
146
+ ```
172
147
 
173
- ```ts
174
- import { useAccept } from '@wooksjs/event-http'
148
+ ### `useLogger(ctx?): Logger`
175
149
 
176
- app.get('/data', () => {
177
- const { acceptsJson, acceptsHtml, acceptsXml, acceptsText, accepts } = useAccept()
150
+ Logger from the current event context. Re-exported from `@wooksjs/event-core`.
178
151
 
179
- if (acceptsJson()) {
180
- return { data: 'json response' }
181
- }
182
- if (acceptsHtml()) {
183
- return '<html><body>HTML response</body></html>'
184
- }
185
- if (accepts('image/png')) {
186
- // custom MIME check
187
- }
152
+ ```ts
153
+ import { useLogger } from '@wooksjs/event-http'
188
154
 
189
- return 'plain text fallback'
190
- })
155
+ const log = useLogger()
156
+ log.info('handling request')
191
157
  ```
192
158
 
193
- ## `useEventId()`
159
+ ## Common Patterns
194
160
 
195
- Generate a unique UUID for the current request (lazy, cached):
161
+ ### Pattern: Auth guard with early return
196
162
 
197
163
  ```ts
198
- import { useEventId } from '@wooksjs/event-http'
164
+ app.post('/admin/action', async () => {
165
+ const { authIs, authRawCredentials } = useAuthorization()
166
+ if (!authIs('bearer')) throw new HttpError(401)
167
+
168
+ const token = authRawCredentials()!
169
+ const user = await verifyToken(token)
170
+ if (!user.isAdmin) throw new HttpError(403)
199
171
 
200
- app.get('/track', () => {
201
- const { getId } = useEventId()
202
- return { requestId: getId() } // '550e8400-e29b-41d4-a716-446655440000'
172
+ // Body is never parsed if auth fails
173
+ const { parseBody } = useBody() // from @wooksjs/http-body
174
+ return parseBody()
203
175
  })
204
176
  ```
205
177
 
206
- The UUID is generated on first call to `getId()` and cached for the request lifetime.
178
+ ### Pattern: Performance resolve context once
179
+
180
+ ```ts
181
+ import { current } from '@wooksjs/event-core'
182
+
183
+ app.get('/hot-path', () => {
184
+ const ctx = current()
185
+ const { method } = useRequest(ctx)
186
+ const { getCookie } = useCookies(ctx)
187
+ const { urlSearchParams } = useSearchParams(ctx)
188
+ // 1 ALS lookup instead of 3
189
+ })
190
+ ```
207
191
 
208
192
  ## Best Practices
209
193
 
210
- - **Use `useHeaders()` for raw header access**, `useAuthorization()` / `useCookies()` / `useAccept()` for parsed access. Don't manually parse headers when a composable exists.
211
- - **Call `rawBody()` only once per request** it reads the stream and returns a Buffer. The result is cached, so subsequent calls return the same promise.
212
- - **Set limits before reading the body** — Call `setMaxCompressed()` etc. before `rawBody()` if you need non-default limits.
213
- - **Use `getIp({ trustProxy: true })` only behind a trusted reverse proxy** otherwise clients can spoof the `X-Forwarded-For` header.
194
+ - Composables are lazy call them only when you need the data
195
+ - Pass `ctx` explicitly in hot paths with multiple composable calls
196
+ - Use `getCookie(name)` over parsing all cookies when you need only a few
197
+ - `rawBody()` handles decompression (gzip, deflate, brotli) automatically with limits enforcement
214
198
 
215
199
  ## Gotchas
216
200
 
217
- - `useHeaders()` returns the raw Node.js `IncomingHttpHeaders` where all header names are lowercase.
218
- - `getCookie()` returns `null` (not `undefined`) when a cookie doesn't exist.
219
- - `rawBody()` returns a `Promise<Buffer>`. If the body is compressed (gzip/deflate/br), it is automatically decompressed.
220
- - Body reading has a 10-second timeout by default. If the client sends data slowly, you may need to increase it with `setReadTimeoutMs()`.
201
+ - `rawBody()` returns a `Promise<Buffer>` always `await` it
202
+ - `rawBody()` consumes the stream it's cached, so second call returns the same buffer
203
+ - `getCookie()` returns `null` (not `undefined`) when a cookie doesn't exist
204
+ - `useRequest()` limits setters use copy-on-write they don't affect other requests