@wooksjs/event-http 0.6.2 → 0.6.4
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 +24 -0
- package/dist/index.cjs +132 -25
- package/dist/index.d.ts +110 -12
- package/dist/index.mjs +132 -25
- package/package.json +45 -37
- package/scripts/setup-skills.js +77 -0
- package/skills/wooksjs-event-http/SKILL.md +37 -0
- package/skills/wooksjs-event-http/addons.md +307 -0
- package/skills/wooksjs-event-http/core.md +297 -0
- package/skills/wooksjs-event-http/error-handling.md +253 -0
- package/skills/wooksjs-event-http/event-core.md +562 -0
- package/skills/wooksjs-event-http/request.md +220 -0
- package/skills/wooksjs-event-http/response.md +336 -0
- package/skills/wooksjs-event-http/routing.md +412 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Request Utilities — @wooksjs/event-http
|
|
2
|
+
|
|
3
|
+
> Covers composables for reading incoming request data: headers, cookies, query params, authorization, IP address, and Accept header.
|
|
4
|
+
|
|
5
|
+
## `useRequest()`
|
|
6
|
+
|
|
7
|
+
Primary composable for accessing the raw incoming HTTP request.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { useRequest } from '@wooksjs/event-http'
|
|
11
|
+
|
|
12
|
+
app.get('/info', () => {
|
|
13
|
+
const { method, url, headers, rawBody, reqId, getIp } = useRequest()
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
method, // 'GET'
|
|
17
|
+
url, // '/info?page=1'
|
|
18
|
+
host: headers.host,
|
|
19
|
+
ip: getIp(),
|
|
20
|
+
requestId: reqId(),
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Properties & methods
|
|
26
|
+
|
|
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 |
|
|
38
|
+
|
|
39
|
+
### Body size limits
|
|
40
|
+
|
|
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 |
|
|
47
|
+
|
|
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):
|
|
49
|
+
|
|
50
|
+
```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
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### IP address with proxy support
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const { getIp } = useRequest()
|
|
68
|
+
|
|
69
|
+
// Direct connection IP
|
|
70
|
+
const ip = getIp()
|
|
71
|
+
|
|
72
|
+
// Trust X-Forwarded-For header (behind reverse proxy)
|
|
73
|
+
const clientIp = getIp({ trustProxy: true })
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## `useHeaders()`
|
|
77
|
+
|
|
78
|
+
Returns incoming request headers directly:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { useHeaders } from '@wooksjs/event-http'
|
|
82
|
+
|
|
83
|
+
app.get('/check', () => {
|
|
84
|
+
const { host, authorization, 'content-type': contentType } = useHeaders()
|
|
85
|
+
return { host, hasAuth: !!authorization }
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Returns a standard `IncomingHttpHeaders` object (same as `req.headers`).
|
|
90
|
+
|
|
91
|
+
## `useSearchParams()`
|
|
92
|
+
|
|
93
|
+
Access URL query parameters (lazy-parsed, cached):
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { useSearchParams } from '@wooksjs/event-http'
|
|
97
|
+
|
|
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']
|
|
104
|
+
|
|
105
|
+
// As a plain object (handles repeated keys as arrays)
|
|
106
|
+
const allParams = jsonSearchParams() // { page: '1', tag: ['a', 'b'] }
|
|
107
|
+
|
|
108
|
+
// Raw query string
|
|
109
|
+
const raw = rawSearchParams() // '?page=1&tag=a&tag=b'
|
|
110
|
+
|
|
111
|
+
return { page, tags, allParams }
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## `useCookies()`
|
|
116
|
+
|
|
117
|
+
Parse incoming request cookies (lazy per-cookie parsing):
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { useCookies } from '@wooksjs/event-http'
|
|
121
|
+
|
|
122
|
+
app.get('/dashboard', () => {
|
|
123
|
+
const { getCookie, rawCookies } = useCookies()
|
|
124
|
+
|
|
125
|
+
const session = getCookie('session_id') // 'abc123' or null
|
|
126
|
+
const theme = getCookie('theme') // 'dark' or null
|
|
127
|
+
|
|
128
|
+
return { session, theme }
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
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()`
|
|
135
|
+
|
|
136
|
+
Parse the `Authorization` header (lazy, cached):
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { useAuthorization } from '@wooksjs/event-http'
|
|
140
|
+
|
|
141
|
+
app.get('/protected', () => {
|
|
142
|
+
const { isBearer, isBasic, authType, authRawCredentials, basicCredentials } = useAuthorization()
|
|
143
|
+
|
|
144
|
+
if (isBearer()) {
|
|
145
|
+
const token = authRawCredentials() // 'eyJhbGciOi...'
|
|
146
|
+
// validate JWT
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isBasic()) {
|
|
150
|
+
const { username, password } = basicCredentials()!
|
|
151
|
+
// validate credentials
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { authType: authType() } // 'Bearer', 'Basic', etc.
|
|
155
|
+
})
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Methods
|
|
159
|
+
|
|
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 |
|
|
168
|
+
|
|
169
|
+
## `useAccept()`
|
|
170
|
+
|
|
171
|
+
Check the `Accept` header for content negotiation:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { useAccept } from '@wooksjs/event-http'
|
|
175
|
+
|
|
176
|
+
app.get('/data', () => {
|
|
177
|
+
const { acceptsJson, acceptsHtml, acceptsXml, acceptsText, accepts } = useAccept()
|
|
178
|
+
|
|
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
|
+
}
|
|
188
|
+
|
|
189
|
+
return 'plain text fallback'
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## `useEventId()`
|
|
194
|
+
|
|
195
|
+
Generate a unique UUID for the current request (lazy, cached):
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { useEventId } from '@wooksjs/event-http'
|
|
199
|
+
|
|
200
|
+
app.get('/track', () => {
|
|
201
|
+
const { getId } = useEventId()
|
|
202
|
+
return { requestId: getId() } // '550e8400-e29b-41d4-a716-446655440000'
|
|
203
|
+
})
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The UUID is generated on first call to `getId()` and cached for the request lifetime.
|
|
207
|
+
|
|
208
|
+
## Best Practices
|
|
209
|
+
|
|
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.
|
|
214
|
+
|
|
215
|
+
## Gotchas
|
|
216
|
+
|
|
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()`.
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# Response & Status — @wooksjs/event-http
|
|
2
|
+
|
|
3
|
+
> Covers setting status codes, response headers, outgoing cookies, cache control, and content type.
|
|
4
|
+
|
|
5
|
+
## Concepts
|
|
6
|
+
|
|
7
|
+
In Wooks, the response is built through composables rather than mutating the `res` object directly. You call `useResponse()`, `useSetHeaders()`, `useSetCookies()`, etc. to configure the response. All settings are collected in the context store and applied when the framework sends the response.
|
|
8
|
+
|
|
9
|
+
The framework automatically handles content type detection, serialization, and status code defaults. You only need to explicitly set these when you want non-default behavior.
|
|
10
|
+
|
|
11
|
+
## `useResponse()`
|
|
12
|
+
|
|
13
|
+
Core response composable for status codes and raw response access:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { useResponse } from '@wooksjs/event-http'
|
|
17
|
+
|
|
18
|
+
app.post('/users', () => {
|
|
19
|
+
const { status } = useResponse()
|
|
20
|
+
status(201) // Set status to 201 Created
|
|
21
|
+
return { id: 1, name: 'Alice' }
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Properties & methods
|
|
26
|
+
|
|
27
|
+
| Name | Type | Description |
|
|
28
|
+
|------|------|-------------|
|
|
29
|
+
| `status(code?)` | `(code?) => EHttpStatusCode` | Get/set the response status code |
|
|
30
|
+
| `rawResponse(opts?)` | `(opts?) => ServerResponse` | Access the raw Node.js response |
|
|
31
|
+
| `hasResponded()` | `() => boolean` | True if response already sent |
|
|
32
|
+
|
|
33
|
+
### `status()` as a hookable function
|
|
34
|
+
|
|
35
|
+
The `status` function doubles as a hookable accessor:
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
const { status } = useResponse()
|
|
39
|
+
|
|
40
|
+
// Set status
|
|
41
|
+
status(404)
|
|
42
|
+
|
|
43
|
+
// Read status (call without args)
|
|
44
|
+
const currentStatus = status()
|
|
45
|
+
|
|
46
|
+
// Or use .value (hooked property)
|
|
47
|
+
status.value = 200
|
|
48
|
+
console.log(status.value) // 200
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Raw response access
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const { rawResponse } = useResponse()
|
|
55
|
+
|
|
56
|
+
// Passthrough mode: lets you write directly but still uses framework headers
|
|
57
|
+
const res = rawResponse({ passthrough: true })
|
|
58
|
+
res.write('chunk 1')
|
|
59
|
+
|
|
60
|
+
// Default mode: marks response as "handled", framework won't write again
|
|
61
|
+
const res2 = rawResponse()
|
|
62
|
+
res2.writeHead(200)
|
|
63
|
+
res2.end('done')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## `useStatus()`
|
|
67
|
+
|
|
68
|
+
Standalone status hook — returns a hookable accessor for the status code:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { useStatus } from '@wooksjs/event-http'
|
|
72
|
+
|
|
73
|
+
app.get('/check', () => {
|
|
74
|
+
const statusHook = useStatus()
|
|
75
|
+
statusHook.value = 202
|
|
76
|
+
return 'Accepted'
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This is useful when a utility function needs to set the status without pulling in the full `useResponse()`.
|
|
81
|
+
|
|
82
|
+
### Type: `TStatusHook`
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import type { TStatusHook } from '@wooksjs/event-http'
|
|
86
|
+
|
|
87
|
+
function myMiddleware(status: TStatusHook) {
|
|
88
|
+
status.value = 403
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## `useSetHeaders()`
|
|
93
|
+
|
|
94
|
+
Set outgoing response headers:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { useSetHeaders } from '@wooksjs/event-http'
|
|
98
|
+
|
|
99
|
+
app.get('/data', () => {
|
|
100
|
+
const { setHeader, setContentType, enableCors } = useSetHeaders()
|
|
101
|
+
|
|
102
|
+
setHeader('x-request-id', '12345')
|
|
103
|
+
setContentType('application/xml')
|
|
104
|
+
enableCors('https://example.com')
|
|
105
|
+
|
|
106
|
+
return '<data>hello</data>'
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Methods
|
|
111
|
+
|
|
112
|
+
| Name | Signature | Description |
|
|
113
|
+
|------|-----------|-------------|
|
|
114
|
+
| `setHeader` | `(name, value) => void` | Set a response header |
|
|
115
|
+
| `getHeader` | `(name) => string \| undefined` | Read a previously set header |
|
|
116
|
+
| `removeHeader` | `(name) => void` | Remove a set header |
|
|
117
|
+
| `setContentType` | `(value) => void` | Shortcut for `setHeader('content-type', value)` |
|
|
118
|
+
| `headers` | `() => Record<string, string>` | Get all set headers as an object |
|
|
119
|
+
| `enableCors` | `(origin?) => void` | Set `Access-Control-Allow-Origin` (default `*`) |
|
|
120
|
+
|
|
121
|
+
## `useSetHeader(name)`
|
|
122
|
+
|
|
123
|
+
Returns a hookable accessor for a single response header:
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import { useSetHeader } from '@wooksjs/event-http'
|
|
127
|
+
|
|
128
|
+
const xRequestId = useSetHeader('x-request-id')
|
|
129
|
+
xRequestId.value = '12345'
|
|
130
|
+
console.log(xRequestId.value) // '12345'
|
|
131
|
+
console.log(xRequestId.isDefined) // true
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Type: `THeaderHook`
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import type { THeaderHook } from '@wooksjs/event-http'
|
|
138
|
+
|
|
139
|
+
function setCorrelationId(header: THeaderHook) {
|
|
140
|
+
if (!header.isDefined) {
|
|
141
|
+
header.value = generateId()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## `useSetCookies()`
|
|
147
|
+
|
|
148
|
+
Set outgoing response cookies:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { useSetCookies } from '@wooksjs/event-http'
|
|
152
|
+
|
|
153
|
+
app.post('/login', () => {
|
|
154
|
+
const { setCookie, removeCookie, clearCookies } = useSetCookies()
|
|
155
|
+
|
|
156
|
+
setCookie('session_id', 'abc123', {
|
|
157
|
+
httpOnly: true,
|
|
158
|
+
secure: true,
|
|
159
|
+
sameSite: 'Strict',
|
|
160
|
+
maxAge: '7d', // supports time strings like '1h', '30m', '7d'
|
|
161
|
+
path: '/',
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
return { success: true }
|
|
165
|
+
})
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Methods
|
|
169
|
+
|
|
170
|
+
| Name | Signature | Description |
|
|
171
|
+
|------|-----------|-------------|
|
|
172
|
+
| `setCookie` | `(name, value, attrs?) => void` | Set a response cookie |
|
|
173
|
+
| `getCookie` | `(name) => TSetCookieData \| undefined` | Read a previously set cookie |
|
|
174
|
+
| `removeCookie` | `(name) => void` | Remove a set cookie |
|
|
175
|
+
| `clearCookies` | `() => void` | Remove all set cookies |
|
|
176
|
+
| `cookies` | `() => string[]` | Render all cookies as Set-Cookie header strings |
|
|
177
|
+
|
|
178
|
+
### Cookie Attributes
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
interface TCookieAttributes {
|
|
182
|
+
expires: Date | string | number // expiration date
|
|
183
|
+
maxAge: number | TTimeMultiString // max age (seconds or time string)
|
|
184
|
+
domain: string // cookie domain
|
|
185
|
+
path: string // cookie path
|
|
186
|
+
secure: boolean // HTTPS only
|
|
187
|
+
httpOnly: boolean // no JS access
|
|
188
|
+
sameSite: boolean | 'Lax' | 'None' | 'Strict'
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Time strings (`TTimeMultiString`) support formats like `'1h'`, `'30m'`, `'7d'`, `'1y'`.
|
|
193
|
+
|
|
194
|
+
## `useSetCookie(name)`
|
|
195
|
+
|
|
196
|
+
Hookable accessor for a single outgoing cookie:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { useSetCookie } from '@wooksjs/event-http'
|
|
200
|
+
|
|
201
|
+
const sessionCookie = useSetCookie('session_id')
|
|
202
|
+
|
|
203
|
+
// Set value
|
|
204
|
+
sessionCookie.value = 'abc123'
|
|
205
|
+
|
|
206
|
+
// Set attributes
|
|
207
|
+
sessionCookie.attrs = { httpOnly: true, secure: true }
|
|
208
|
+
|
|
209
|
+
// Read
|
|
210
|
+
console.log(sessionCookie.value) // 'abc123'
|
|
211
|
+
console.log(sessionCookie.attrs) // { httpOnly: true, secure: true }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Type: `TCookieHook`
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import type { TCookieHook } from '@wooksjs/event-http'
|
|
218
|
+
|
|
219
|
+
function enforceSecureCookie(cookie: TCookieHook) {
|
|
220
|
+
cookie.attrs = { ...cookie.attrs, secure: true, httpOnly: true }
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## `useSetCacheControl()`
|
|
225
|
+
|
|
226
|
+
Set cache-related response headers:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
import { useSetCacheControl } from '@wooksjs/event-http'
|
|
230
|
+
|
|
231
|
+
app.get('/assets/:file', () => {
|
|
232
|
+
const { setCacheControl, setExpires, setAge, setPragmaNoCache } = useSetCacheControl()
|
|
233
|
+
|
|
234
|
+
setCacheControl({
|
|
235
|
+
maxAge: 3600,
|
|
236
|
+
public: true,
|
|
237
|
+
noTransform: true,
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
// Or set individual cache headers
|
|
241
|
+
setAge(300) // Age: 300
|
|
242
|
+
setExpires(new Date('2025-12-31')) // Expires: Wed, 31 Dec 2025 ...
|
|
243
|
+
setPragmaNoCache() // Pragma: no-cache
|
|
244
|
+
|
|
245
|
+
return serveFile(...)
|
|
246
|
+
})
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Cache-Control Directives
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
interface TCacheControl {
|
|
253
|
+
maxAge?: number | TTimeMultiString
|
|
254
|
+
sMaxage?: number | TTimeMultiString
|
|
255
|
+
noCache?: boolean
|
|
256
|
+
noStore?: boolean
|
|
257
|
+
noTransform?: boolean
|
|
258
|
+
mustRevalidate?: boolean
|
|
259
|
+
proxyRevalidate?: boolean
|
|
260
|
+
public?: boolean
|
|
261
|
+
private?: boolean
|
|
262
|
+
immutable?: boolean
|
|
263
|
+
staleWhileRevalidate?: number | TTimeMultiString
|
|
264
|
+
staleIfError?: number | TTimeMultiString
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Content Type Auto-Detection
|
|
269
|
+
|
|
270
|
+
The framework automatically sets the content type based on the handler return value:
|
|
271
|
+
|
|
272
|
+
| Return type | Content-Type |
|
|
273
|
+
|-------------|-------------|
|
|
274
|
+
| `string` | `text/plain` |
|
|
275
|
+
| `number` | `text/plain` |
|
|
276
|
+
| `boolean` | `text/plain` |
|
|
277
|
+
| `object` / `array` | `application/json` |
|
|
278
|
+
| `Readable` stream | (must set manually) |
|
|
279
|
+
| `undefined` | (no body, 204) |
|
|
280
|
+
|
|
281
|
+
Override by calling `setContentType()` before returning:
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
app.get('/html', () => {
|
|
285
|
+
const { setContentType } = useSetHeaders()
|
|
286
|
+
setContentType('text/html')
|
|
287
|
+
return '<h1>Hello</h1>'
|
|
288
|
+
})
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Common Patterns
|
|
292
|
+
|
|
293
|
+
### Pattern: JSON API Response
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
app.get('/api/users/:id', async () => {
|
|
297
|
+
const { get } = useRouteParams<{ id: string }>()
|
|
298
|
+
const user = await db.findUser(get('id'))
|
|
299
|
+
if (!user) throw new HttpError(404, 'User not found')
|
|
300
|
+
return user // auto-serialized as JSON with 200
|
|
301
|
+
})
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Pattern: File Download
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
app.get('/download/:file', () => {
|
|
308
|
+
const { setHeader } = useSetHeaders()
|
|
309
|
+
const { get } = useRouteParams<{ file: string }>()
|
|
310
|
+
setHeader('content-disposition', `attachment; filename="${get('file')}"`)
|
|
311
|
+
return createReadStream(`/uploads/${get('file')}`)
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Pattern: Redirect
|
|
316
|
+
|
|
317
|
+
```ts
|
|
318
|
+
import { BaseHttpResponse } from '@wooksjs/event-http'
|
|
319
|
+
|
|
320
|
+
app.get('/old-page', () => {
|
|
321
|
+
return new BaseHttpResponse().setStatus(302).setHeader('location', '/new-page')
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Best Practices
|
|
326
|
+
|
|
327
|
+
- **Let the framework auto-detect content type** — Only call `setContentType()` when you need a non-default type.
|
|
328
|
+
- **Use `useSetCookies()` for multiple cookies, `useSetCookie(name)` for hookable access to a single cookie**.
|
|
329
|
+
- **Set status before returning** — If you need a custom status, call `status(code)` before the handler returns.
|
|
330
|
+
- **Use time strings for maxAge** — `'7d'` is clearer than `604800`.
|
|
331
|
+
|
|
332
|
+
## Gotchas
|
|
333
|
+
|
|
334
|
+
- If you call `rawResponse()` without `{ passthrough: true }`, the framework marks the response as sent and won't write anything else.
|
|
335
|
+
- Headers set via `useSetHeaders()` are merged with any `BaseHttpResponse` headers. The `BaseHttpResponse` headers take precedence on collision.
|
|
336
|
+
- Cookies set via `useSetCookies()` are merged with `BaseHttpResponse.setCookie()` cookies.
|