@wooksjs/event-http 0.6.6 → 0.7.1
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 +11 -45
- package/dist/index.cjs +1148 -968
- package/dist/index.d.ts +364 -308
- package/dist/index.mjs +1104 -920
- package/package.json +7 -7
- package/skills/wooksjs-event-http/SKILL.md +28 -21
- package/skills/wooksjs-event-http/core.md +83 -228
- package/skills/wooksjs-event-http/request.md +131 -147
- package/skills/wooksjs-event-http/response.md +166 -235
- package/skills/wooksjs-event-http/testing.md +150 -0
- package/skills/wooksjs-event-http/addons.md +0 -307
- package/skills/wooksjs-event-http/error-handling.md +0 -253
- package/skills/wooksjs-event-http/event-core.md +0 -562
- package/skills/wooksjs-event-http/routing.md +0 -412
|
@@ -1,220 +1,204 @@
|
|
|
1
|
-
# Request
|
|
1
|
+
# Request composables — @wooksjs/event-http
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Reading request data: headers, cookies, query params, body, authorization, IP address.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Concepts
|
|
6
6
|
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
import { useRequest } from '@wooksjs/event-http'
|
|
9
|
+
All composables are importable from `@wooksjs/event-http`.
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
const { method, url, headers, rawBody, reqId, getIp } = useRequest()
|
|
11
|
+
## API Reference
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
17
|
+
```ts
|
|
18
|
+
import { useRequest } from '@wooksjs/event-http'
|
|
26
19
|
|
|
27
|
-
|
|
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
|
-
|
|
23
|
+
**Returned properties:**
|
|
40
24
|
|
|
41
|
-
|
|
|
42
|
-
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
46
|
-
| `
|
|
25
|
+
| Property | Type | Description |
|
|
26
|
+
| ---------------- | ------------------------------------------------- | -------------------------------------------------- |
|
|
27
|
+
| `raw` | `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
|
-
|
|
37
|
+
**Request limits (per-request override):**
|
|
49
38
|
|
|
50
39
|
```ts
|
|
51
|
-
const {
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
45
|
+
Default limits: `maxCompressed: 1MB`, `maxInflated: 10MB`, `maxRatio: 100`, `readTimeoutMs: 10s`.
|
|
65
46
|
|
|
66
|
-
|
|
67
|
-
const { getIp } = useRequest()
|
|
47
|
+
### `useHeaders(ctx?): IncomingHttpHeaders`
|
|
68
48
|
|
|
69
|
-
|
|
70
|
-
const ip = getIp()
|
|
49
|
+
Returns request headers directly. Shorthand for `useRequest().headers`.
|
|
71
50
|
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
```ts
|
|
52
|
+
import { useHeaders } from '@wooksjs/event-http'
|
|
53
|
+
|
|
54
|
+
const { host, authorization, 'content-type': contentType } = useHeaders()
|
|
74
55
|
```
|
|
75
56
|
|
|
76
|
-
|
|
57
|
+
### `useCookies(ctx?)`
|
|
77
58
|
|
|
78
|
-
|
|
59
|
+
Parses incoming request cookies lazily (per cookie name, via `cachedBy`).
|
|
79
60
|
|
|
80
61
|
```ts
|
|
81
|
-
import {
|
|
62
|
+
import { useCookies } from '@wooksjs/event-http'
|
|
82
63
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
64
|
+
const { getCookie, raw } = useCookies()
|
|
65
|
+
const session = getCookie('session_id') // parsed + cached
|
|
66
|
+
const theme = getCookie('theme') // parsed + cached (different key)
|
|
67
|
+
// raw = raw Cookie header string
|
|
87
68
|
```
|
|
88
69
|
|
|
89
|
-
|
|
70
|
+
### `useUrlParams(ctx?)`
|
|
90
71
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Access URL query parameters (lazy-parsed, cached):
|
|
72
|
+
Provides access to URL query parameters.
|
|
94
73
|
|
|
95
74
|
```ts
|
|
96
|
-
import {
|
|
97
|
-
|
|
98
|
-
app.get('/search', () => {
|
|
99
|
-
const { urlSearchParams, jsonSearchParams, rawSearchParams } = useSearchParams()
|
|
75
|
+
import { useUrlParams } from '@wooksjs/event-http'
|
|
100
76
|
|
|
101
|
-
|
|
102
|
-
const page = urlSearchParams().get('page') // '1'
|
|
103
|
-
const tags = urlSearchParams().getAll('tag') // ['a', 'b']
|
|
77
|
+
const { params, toJson, raw } = useUrlParams()
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
|
|
79
|
+
// URLSearchParams API
|
|
80
|
+
const page = params().get('page')
|
|
81
|
+
const tags = params().getAll('tag')
|
|
107
82
|
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
// As a plain object
|
|
84
|
+
const query = toJson() // { page: '1', tag: ['a', 'b'] }
|
|
110
85
|
|
|
111
|
-
|
|
112
|
-
|
|
86
|
+
// Raw query string
|
|
87
|
+
const rawQuery = raw() // '?page=1&tag=a&tag=b'
|
|
113
88
|
```
|
|
114
89
|
|
|
115
|
-
|
|
90
|
+
### `useAuthorization(ctx?)`
|
|
116
91
|
|
|
117
|
-
|
|
92
|
+
Parses the Authorization header (supports Basic and Bearer).
|
|
118
93
|
|
|
119
94
|
```ts
|
|
120
|
-
import {
|
|
95
|
+
import { useAuthorization } from '@wooksjs/event-http'
|
|
121
96
|
|
|
122
|
-
|
|
123
|
-
const { getCookie, rawCookies } = useCookies()
|
|
97
|
+
const { authorization, type, credentials, is, basicCredentials } = useAuthorization()
|
|
124
98
|
|
|
125
|
-
|
|
126
|
-
const
|
|
99
|
+
if (is('bearer')) {
|
|
100
|
+
const token = credentials() // the raw token string
|
|
101
|
+
}
|
|
127
102
|
|
|
128
|
-
|
|
129
|
-
})
|
|
103
|
+
if (is('basic')) {
|
|
104
|
+
const { username, password } = basicCredentials()!
|
|
105
|
+
}
|
|
130
106
|
```
|
|
131
107
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
## `useAuthorization()`
|
|
108
|
+
**Returned properties:**
|
|
135
109
|
|
|
136
|
-
|
|
110
|
+
| Property | Type | Description |
|
|
111
|
+
| ---------------------- | -------------------------------- | -------------------------------------------------------------- |
|
|
112
|
+
| `authorization` | `string \| undefined` | Raw Authorization header value |
|
|
113
|
+
| `type()` | `string \| null` | Auth scheme: `'Basic'`, `'Bearer'`, etc. |
|
|
114
|
+
| `credentials()` | `string \| null` | Everything after the scheme |
|
|
115
|
+
| `is(type)` | `boolean` | Check auth scheme: `'basic'`, `'bearer'`, or any custom scheme |
|
|
116
|
+
| `basicCredentials()` | `{ username, password } \| null` | Decoded Basic credentials |
|
|
137
117
|
|
|
138
|
-
|
|
139
|
-
import { useAuthorization } from '@wooksjs/event-http'
|
|
118
|
+
### `useAccept(ctx?)`
|
|
140
119
|
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
// validate JWT
|
|
147
|
-
}
|
|
122
|
+
```ts
|
|
123
|
+
import { useAccept } from '@wooksjs/event-http'
|
|
148
124
|
|
|
149
|
-
|
|
150
|
-
const { username, password } = basicCredentials()!
|
|
151
|
-
// validate credentials
|
|
152
|
-
}
|
|
125
|
+
const { accept, has } = useAccept()
|
|
153
126
|
|
|
154
|
-
|
|
155
|
-
|
|
127
|
+
if (has('json')) {
|
|
128
|
+
/* ... */
|
|
129
|
+
}
|
|
130
|
+
if (has('image/png')) {
|
|
131
|
+
/* ... */
|
|
132
|
+
}
|
|
156
133
|
```
|
|
157
134
|
|
|
158
|
-
###
|
|
135
|
+
### `useRouteParams<T>(ctx?)`
|
|
159
136
|
|
|
160
|
-
|
|
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
|
-
|
|
139
|
+
```ts
|
|
140
|
+
import { useRouteParams } from '@wooksjs/event-http'
|
|
170
141
|
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
import { useAccept } from '@wooksjs/event-http'
|
|
148
|
+
### `useLogger(ctx?): Logger`
|
|
175
149
|
|
|
176
|
-
|
|
177
|
-
const { acceptsJson, acceptsHtml, acceptsXml, acceptsText, accepts } = useAccept()
|
|
150
|
+
Logger from the current event context. Re-exported from `@wooksjs/event-core`.
|
|
178
151
|
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
|
|
190
|
-
|
|
155
|
+
const log = useLogger()
|
|
156
|
+
log.info('handling request')
|
|
191
157
|
```
|
|
192
158
|
|
|
193
|
-
##
|
|
159
|
+
## Common Patterns
|
|
194
160
|
|
|
195
|
-
|
|
161
|
+
### Pattern: Auth guard with early return
|
|
196
162
|
|
|
197
163
|
```ts
|
|
198
|
-
|
|
164
|
+
app.post('/admin/action', async () => {
|
|
165
|
+
const { is, credentials } = useAuthorization()
|
|
166
|
+
if (!is('bearer')) throw new HttpError(401)
|
|
167
|
+
|
|
168
|
+
const token = credentials()!
|
|
169
|
+
const user = await verifyToken(token)
|
|
170
|
+
if (!user.isAdmin) throw new HttpError(403)
|
|
199
171
|
|
|
200
|
-
|
|
201
|
-
const {
|
|
202
|
-
return
|
|
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
|
-
|
|
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 { params } = useUrlParams(ctx)
|
|
188
|
+
// 1 ALS lookup instead of 3
|
|
189
|
+
})
|
|
190
|
+
```
|
|
207
191
|
|
|
208
192
|
## Best Practices
|
|
209
193
|
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
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
|
-
- `
|
|
218
|
-
- `
|
|
219
|
-
- `
|
|
220
|
-
-
|
|
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
|