@shaferllc/keel 0.66.0 → 0.68.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/AGENTS.md +167 -0
- package/README.md +30 -1
- package/bin/keel-mcp.mjs +9 -0
- package/dist/core/application.d.ts +5 -5
- package/dist/core/application.js +2 -2
- package/dist/core/cache.d.ts +82 -5
- package/dist/core/cache.js +181 -23
- package/dist/core/cli/stubs.d.ts +12 -0
- package/dist/core/cli/stubs.js +120 -0
- package/dist/core/events.d.ts +129 -5
- package/dist/core/events.js +165 -7
- package/dist/core/health.d.ts +141 -0
- package/dist/core/health.js +226 -0
- package/dist/core/helpers.d.ts +9 -3
- package/dist/core/helpers.js +11 -3
- package/dist/core/index.d.ts +15 -10
- package/dist/core/index.js +7 -4
- package/dist/core/lock.d.ts +139 -0
- package/dist/core/lock.js +215 -0
- package/dist/core/logger.d.ts +82 -4
- package/dist/core/logger.js +141 -23
- package/dist/core/mail.d.ts +128 -7
- package/dist/core/mail.js +264 -16
- package/dist/core/queue.d.ts +134 -9
- package/dist/core/queue.js +304 -14
- package/dist/core/storage.d.ts +159 -6
- package/dist/core/storage.js +287 -7
- package/dist/mcp/server.d.ts +19 -0
- package/dist/mcp/server.js +355 -0
- package/docs/ai-manifest.json +2472 -0
- package/docs/ai.md +128 -0
- package/docs/architecture.md +331 -0
- package/docs/authentication.md +453 -0
- package/docs/authorization.md +167 -0
- package/docs/broadcasting.md +137 -0
- package/docs/broker.md +500 -0
- package/docs/cache.md +558 -0
- package/docs/configuration.md +311 -0
- package/docs/console.md +356 -0
- package/docs/container.md +467 -0
- package/docs/controllers.md +265 -0
- package/docs/cors.md +51 -0
- package/docs/database.md +530 -0
- package/docs/debugging.md +129 -0
- package/docs/decorators.md +127 -0
- package/docs/errors.md +395 -0
- package/docs/events.md +496 -0
- package/docs/examples/architecture-app.ts +27 -0
- package/docs/examples/authentication.ts +61 -0
- package/docs/examples/authorization.ts +79 -0
- package/docs/examples/broadcasting.ts +60 -0
- package/docs/examples/broker-cache-validate.ts +34 -0
- package/docs/examples/broker-fault-tolerance.ts +29 -0
- package/docs/examples/broker-middleware.ts +27 -0
- package/docs/examples/broker.ts +203 -0
- package/docs/examples/cache.ts +222 -0
- package/docs/examples/configuration.ts +81 -0
- package/docs/examples/container.ts +134 -0
- package/docs/examples/controllers.ts +86 -0
- package/docs/examples/database.ts +118 -0
- package/docs/examples/debugging.ts +41 -0
- package/docs/examples/decorators.ts +40 -0
- package/docs/examples/errors.ts +121 -0
- package/docs/examples/events.ts +204 -0
- package/docs/examples/factories.ts +84 -0
- package/docs/examples/hashing.ts +71 -0
- package/docs/examples/health.ts +94 -0
- package/docs/examples/helpers.ts +171 -0
- package/docs/examples/hooks.ts +54 -0
- package/docs/examples/inertia.ts +81 -0
- package/docs/examples/locks.ts +120 -0
- package/docs/examples/logger.ts +92 -0
- package/docs/examples/mail.ts +160 -0
- package/docs/examples/middleware.ts +119 -0
- package/docs/examples/migrations.ts +126 -0
- package/docs/examples/models.ts +239 -0
- package/docs/examples/notification.ts +124 -0
- package/docs/examples/providers.ts +123 -0
- package/docs/examples/queues.ts +254 -0
- package/docs/examples/rate-limiting.ts +42 -0
- package/docs/examples/redis.ts +99 -0
- package/docs/examples/request-response.ts +197 -0
- package/docs/examples/routing.ts +186 -0
- package/docs/examples/scheduling.ts +62 -0
- package/docs/examples/sessions.ts +102 -0
- package/docs/examples/static-files.ts +63 -0
- package/docs/examples/storage.ts +132 -0
- package/docs/examples/templates.ts +58 -0
- package/docs/examples/testing.ts +66 -0
- package/docs/examples/transformer.ts +141 -0
- package/docs/examples/transformers.ts +49 -0
- package/docs/examples/url-builder.ts +86 -0
- package/docs/examples/validation.ts +102 -0
- package/docs/examples/views.tsx +62 -0
- package/docs/examples/vite.ts +106 -0
- package/docs/factories.md +166 -0
- package/docs/getting-started.md +290 -0
- package/docs/hashing.md +259 -0
- package/docs/health.md +225 -0
- package/docs/helpers.md +347 -0
- package/docs/hono.md +186 -0
- package/docs/hooks.md +118 -0
- package/docs/inertia.md +241 -0
- package/docs/locks.md +323 -0
- package/docs/logger.md +290 -0
- package/docs/mail.md +678 -0
- package/docs/middleware.md +425 -0
- package/docs/migrations.md +476 -0
- package/docs/models.md +810 -0
- package/docs/notifications.md +474 -0
- package/docs/providers.md +363 -0
- package/docs/queues.md +679 -0
- package/docs/rate-limiting.md +155 -0
- package/docs/redis.md +178 -0
- package/docs/request-response.md +953 -0
- package/docs/routing.md +804 -0
- package/docs/scheduling.md +110 -0
- package/docs/security.md +85 -0
- package/docs/sessions.md +354 -0
- package/docs/social-auth.md +174 -0
- package/docs/static-files.md +211 -0
- package/docs/storage.md +450 -0
- package/docs/templates.md +315 -0
- package/docs/testing.md +125 -0
- package/docs/transformers.md +381 -0
- package/docs/url-builder.md +295 -0
- package/docs/validation.md +288 -0
- package/docs/views.md +267 -0
- package/docs/vite.md +434 -0
- package/llms-full.txt +17694 -0
- package/llms.txt +116 -0
- package/package.json +26 -7
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
# Request & Response
|
|
2
|
+
|
|
3
|
+
Beyond the terse `param()` / `json()` shortcuts, the `request` and `response`
|
|
4
|
+
accessors give you the full input/output surface — no context threading. They
|
|
5
|
+
resolve the active Hono context from async-context storage, which the HTTP
|
|
6
|
+
kernel enables for every request, so they only work **inside a request**. Call
|
|
7
|
+
one outside a handler and it throws (`json`/`text`/`html`/`redirect` are the
|
|
8
|
+
exception — see below).
|
|
9
|
+
|
|
10
|
+
## Reading input
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { request } from "@shaferllc/keel/core";
|
|
14
|
+
|
|
15
|
+
request.param("id"); // route parameter
|
|
16
|
+
request.query("q"); // query-string value
|
|
17
|
+
request.header("authorization");
|
|
18
|
+
|
|
19
|
+
// merged query + parsed body (async)
|
|
20
|
+
await request.all(); // { …query, …body }
|
|
21
|
+
await request.input("email"); // one value from query or body
|
|
22
|
+
await request.input("page", 1); // with a fallback
|
|
23
|
+
await request.only(["email", "name"]); // a subset
|
|
24
|
+
await request.except(["password"]); // everything but these
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`request.all()` reads the query string and, if the request carries a body,
|
|
28
|
+
merges the parsed body over it — JSON bodies via `req.json()`, form bodies
|
|
29
|
+
(`multipart/form-data` or `application/x-www-form-urlencoded`) via cached
|
|
30
|
+
`FormData`. File fields are dropped from `all()` (reach for `file()`/`files()`).
|
|
31
|
+
A missing or malformed body is swallowed — you just get the query values.
|
|
32
|
+
`input`/`only`/`except` all build on `all()`, so they're async too.
|
|
33
|
+
|
|
34
|
+
`request` also exposes `request.method`, `request.path`, `request.url`,
|
|
35
|
+
`request.status`, `request.ip()`, `request.ips()`, `request.hasBody()`,
|
|
36
|
+
`request.headers()`, and `request.raw` (the underlying web `Request`). For the
|
|
37
|
+
raw parsed JSON body without the query merge, use `request.json<T>()`.
|
|
38
|
+
|
|
39
|
+
For URL and connection introspection there's `request.protocol`,
|
|
40
|
+
`request.secure`, `request.host`, `request.hostname`, `request.origin`,
|
|
41
|
+
`request.fullUrl`, and `request.querystring`. These are **proxy-aware**:
|
|
42
|
+
`X-Forwarded-Proto` / `X-Forwarded-Host` win over the raw request URL, so an app
|
|
43
|
+
behind a TLS-terminating proxy or load balancer sees the client's real scheme
|
|
44
|
+
and host (handy for building absolute links or forcing HTTPS).
|
|
45
|
+
|
|
46
|
+
> `input`/`only`/`except`/`all` are object methods that lean on `this`. Call
|
|
47
|
+
> them off `request` (`request.input(…)`), not destructured (`const { input } =
|
|
48
|
+
> request`), or `this` is lost.
|
|
49
|
+
|
|
50
|
+
### Other content types
|
|
51
|
+
|
|
52
|
+
`all()` understands JSON and form bodies. For anything else — XML, CSV, a binary
|
|
53
|
+
payload, a custom format — read the raw body and parse it yourself. There's no
|
|
54
|
+
content-type parser registry to configure (unlike Fastify): parsing is explicit,
|
|
55
|
+
so you call the accessor you want.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
await request.text(); // the body as a string (XML, CSV, …)
|
|
59
|
+
await request.arrayBuffer(); // the body as bytes (protobuf, msgpack, …)
|
|
60
|
+
await request.blob(); // the body as a Blob
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
r.post("/webhook", async () => {
|
|
65
|
+
const xml = await request.text();
|
|
66
|
+
return response.json(parseXml(xml));
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
These read from the same underlying `Request` as `request.raw`, so a middleware
|
|
71
|
+
can equally parse a custom type once and stash it with `ctx().set("body", …)`.
|
|
72
|
+
|
|
73
|
+
## Route info
|
|
74
|
+
|
|
75
|
+
The kernel stashes the matched route on the context, so you can branch on it:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
request.route; // { name, pattern, methods } | undefined
|
|
79
|
+
request.routeIs("users.show"); // true if the matched route is named that
|
|
80
|
+
request.subdomain("tenant"); // a param captured from a domain-bound route
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## File uploads
|
|
84
|
+
|
|
85
|
+
Uploaded files come back as web-standard `File` objects (works on Node and the
|
|
86
|
+
edge — no temp directory, no streaming to disk):
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
const avatar = await request.file("avatar"); // File | undefined
|
|
90
|
+
const docs = await request.files("docs"); // File[]
|
|
91
|
+
const all = await request.allFiles(); // { field: File | File[] }
|
|
92
|
+
|
|
93
|
+
if (avatar) {
|
|
94
|
+
avatar.name; // "photo.png"
|
|
95
|
+
avatar.size; // bytes
|
|
96
|
+
avatar.type; // "image/png" (client-supplied)
|
|
97
|
+
const bytes = await avatar.arrayBuffer(); // persist via R2/S3/fs yourself
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `FormData` is parsed once per request and cached, so calling `file()`,
|
|
102
|
+
`files()`, `allFiles()`, and `all()` in one handler doesn't re-read the body.
|
|
103
|
+
`allFiles()` groups repeated field names into an array and single fields into a
|
|
104
|
+
lone `File`.
|
|
105
|
+
|
|
106
|
+
Validate a file with your schema (Keel stays schema-agnostic):
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const Upload = z.object({
|
|
110
|
+
avatar: z.instanceof(File).refine((f) => f.size < 2_000_000, "Too large"),
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Content negotiation
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
request.accepts(["application/json", "text/html"]); // best match, or null
|
|
118
|
+
request.types(); // accepted types, ordered
|
|
119
|
+
request.language(["en", "fr"]); // best language, or null
|
|
120
|
+
request.languages();
|
|
121
|
+
request.encoding(["br", "gzip"]); // best content encoding
|
|
122
|
+
request.charset(["utf-8"]); // best charset
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`accepts`/`language`/`encoding`/`charset` parse the relevant `Accept*` header by
|
|
126
|
+
q-weight and return the highest-preference offered value, honoring `*/*` (or
|
|
127
|
+
`*`) as "anything" — which resolves to the first thing you offer. No match
|
|
128
|
+
returns `null`. Each has a plural list form (`types()`, `languages()`,
|
|
129
|
+
`encodings()`, `charsets()`) returning everything the client accepts, ordered by
|
|
130
|
+
preference.
|
|
131
|
+
|
|
132
|
+
## Cookies
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
request.cookie("session"); // one cookie, or undefined
|
|
136
|
+
request.cookie(); // all cookies as an object
|
|
137
|
+
|
|
138
|
+
response.cookie("session", token, { httpOnly: true, maxAge: 3600 });
|
|
139
|
+
response.clearCookie("session");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Writing output
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { response } from "@shaferllc/keel/core";
|
|
146
|
+
|
|
147
|
+
response.json({ ok: true });
|
|
148
|
+
response.text("hello");
|
|
149
|
+
response.html("<h1>Hi</h1>");
|
|
150
|
+
response.redirect("/login");
|
|
151
|
+
response.back("/"); // back to the Referer, else the fallback
|
|
152
|
+
response.send(anything); // objects → JSON, else text
|
|
153
|
+
|
|
154
|
+
response.status(201).json(created); // chainable
|
|
155
|
+
response.header("x-total", "42").json(rows);
|
|
156
|
+
response.type("text/csv").append("vary", "accept");
|
|
157
|
+
response.attachment("report.csv").text(csv); // Content-Disposition download
|
|
158
|
+
response.removeHeader("x-powered-by");
|
|
159
|
+
response.cookie("flash", "saved").redirect("/");
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Every mutator (`status`, `header`, `type`, `attachment`, `append`,
|
|
163
|
+
`removeHeader`, `cookie`, `clearCookie`) returns `response`, so they chain; the
|
|
164
|
+
terminal `json`/`text`/`html`/`redirect`/`back`/`send` produce the `Response`.
|
|
165
|
+
`send` inspects its argument — a non-null object becomes JSON, anything else is
|
|
166
|
+
stringified to text. `back` bounces to the `Referer` header (falling back to the
|
|
167
|
+
argument, default `"/"`); `redirect("back")` is the same shortcut.
|
|
168
|
+
|
|
169
|
+
## Aborting with guards
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
response.abort("Not found", 404); // always
|
|
173
|
+
response.abortIf(!user, "Not found", 404); // if truthy
|
|
174
|
+
response.abortUnless(user.isAdmin, "Forbidden", 403);
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`abort()` throws an `HttpException` (default status `400`), which the kernel
|
|
178
|
+
renders (see [Errors](./errors.md)). `abortIf`/`abortUnless` throw the same,
|
|
179
|
+
conditionally — handy as one-line guards at the top of a handler.
|
|
180
|
+
|
|
181
|
+
## Standalone shortcuts
|
|
182
|
+
|
|
183
|
+
Every reader/writer also exists as a flat helper for terse handlers:
|
|
184
|
+
`json()`, `text()`, `html()`, `redirect()`, `param()`, `query()`, `header()`,
|
|
185
|
+
`body()`. Use whichever reads best — they resolve the same request.
|
|
186
|
+
|
|
187
|
+
The response builders (`json`/`text`/`html`/`redirect`) are special: they work
|
|
188
|
+
**both** inside a handler and standalone. Inside a request they build on the
|
|
189
|
+
context (so status and queued headers/cookies apply); outside one they fall
|
|
190
|
+
back to a plain web `Response`. That's what lets you hand one straight to the
|
|
191
|
+
router as a static route value — `router.get("/ping", json({ ok: true }))` —
|
|
192
|
+
and have it cloned per request.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## API reference
|
|
197
|
+
|
|
198
|
+
### `ctx()`
|
|
199
|
+
|
|
200
|
+
`ctx(): Context`
|
|
201
|
+
|
|
202
|
+
Returns the current Hono `Context` from async-context storage — the escape
|
|
203
|
+
hatch when you need something the accessors don't wrap.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { ctx } from "@shaferllc/keel/core";
|
|
207
|
+
|
|
208
|
+
ctx().req.raw; // the web Request
|
|
209
|
+
ctx().executionCtx; // waitUntil, passThroughOnException, …
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Notes:** throws if called outside a request (nothing has set up the context).
|
|
213
|
+
Everything else in this module is built on it.
|
|
214
|
+
|
|
215
|
+
### `json(data, status?)`
|
|
216
|
+
|
|
217
|
+
`json(data: unknown, status?: number): Response`
|
|
218
|
+
|
|
219
|
+
Serializes `data` to a JSON `Response`.
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { json } from "@shaferllc/keel/core";
|
|
223
|
+
|
|
224
|
+
json({ ok: true });
|
|
225
|
+
json({ error: "nope" }, 422);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Notes:** works inside a handler (builds on the context, applying any queued
|
|
229
|
+
status/headers) and standalone (a plain `Response.json`). Safe as a static
|
|
230
|
+
route value.
|
|
231
|
+
|
|
232
|
+
### `text(body, status?)`
|
|
233
|
+
|
|
234
|
+
`text(body: string, status?: number): Response`
|
|
235
|
+
|
|
236
|
+
Returns a `text/plain; charset=UTF-8` response.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { text } from "@shaferllc/keel/core";
|
|
240
|
+
|
|
241
|
+
text("pong");
|
|
242
|
+
text("rate limited", 429);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**Notes:** dual-mode like `json`. Standalone, it sets the content-type header
|
|
246
|
+
itself.
|
|
247
|
+
|
|
248
|
+
### `html(body, status?)`
|
|
249
|
+
|
|
250
|
+
`html(body: string, status?: number): Response`
|
|
251
|
+
|
|
252
|
+
Returns a `text/html; charset=UTF-8` response.
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { html } from "@shaferllc/keel/core";
|
|
256
|
+
|
|
257
|
+
html("<h1>Hi</h1>");
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Notes:** does not escape `body` — you're responsible for the markup.
|
|
261
|
+
|
|
262
|
+
### `redirect(location, status?)`
|
|
263
|
+
|
|
264
|
+
`redirect(location: string, status?: number): Response`
|
|
265
|
+
|
|
266
|
+
Returns a redirect to `location`.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import { redirect } from "@shaferllc/keel/core";
|
|
270
|
+
|
|
271
|
+
redirect("/login");
|
|
272
|
+
redirect("/", 301);
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Notes:** default status is `302` in standalone mode. Sets the `Location`
|
|
276
|
+
header.
|
|
277
|
+
|
|
278
|
+
### `request`
|
|
279
|
+
|
|
280
|
+
The flat request accessor. You import it as-is (it's a singleton object, not a
|
|
281
|
+
class) and read off it — every access resolves the current context, so it's
|
|
282
|
+
always about the in-flight request.
|
|
283
|
+
|
|
284
|
+
#### `request.method`
|
|
285
|
+
|
|
286
|
+
`get method(): string`
|
|
287
|
+
|
|
288
|
+
The HTTP method (`"GET"`, `"POST"`, …).
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
if (request.method === "POST") { /* … */ }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
#### `request.path`
|
|
295
|
+
|
|
296
|
+
`get path(): string`
|
|
297
|
+
|
|
298
|
+
The request path, without query string.
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
request.path; // "/users/1"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### `request.url`
|
|
305
|
+
|
|
306
|
+
`get url(): string`
|
|
307
|
+
|
|
308
|
+
The full request URL, including query string.
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
request.url; // "https://api.example.com/users/1?tab=posts"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### `request.protocol` · `request.secure` · `request.host` · `request.hostname` · `request.origin` · `request.fullUrl` · `request.querystring`
|
|
315
|
+
|
|
316
|
+
Proxy-aware URL and connection accessors — `X-Forwarded-Proto` and
|
|
317
|
+
`X-Forwarded-Host` take precedence over the raw request URL, so an app behind a
|
|
318
|
+
TLS-terminating proxy sees the client's real scheme and host.
|
|
319
|
+
|
|
320
|
+
```ts
|
|
321
|
+
request.protocol; // "https" (get protocol(): string)
|
|
322
|
+
request.secure; // true (get secure(): boolean — protocol === "https")
|
|
323
|
+
request.host; // "example.com:443" (host with port)
|
|
324
|
+
request.hostname; // "example.com" (host without port)
|
|
325
|
+
request.origin; // "https://example.com"
|
|
326
|
+
request.fullUrl; // "https://example.com/users/1?tab=posts"
|
|
327
|
+
request.querystring; // "tab=posts" (no leading "?", "" when none)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Notes:** `fullUrl` is rebuilt from the (forwarded) origin plus the path and
|
|
331
|
+
query, so it reflects the client-facing URL rather than the internal one the
|
|
332
|
+
proxy dialed. Use `origin` to build absolute links and `secure` to gate or
|
|
333
|
+
redirect insecure requests.
|
|
334
|
+
|
|
335
|
+
#### `request.status`
|
|
336
|
+
|
|
337
|
+
`get status(): number`
|
|
338
|
+
|
|
339
|
+
The current response status — useful in middleware after `await next()`.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
await next();
|
|
343
|
+
if (request.status >= 500) log(request.path);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### `request.header(name)`
|
|
347
|
+
|
|
348
|
+
`header(name: string): string | undefined`
|
|
349
|
+
|
|
350
|
+
A single request header (case-insensitive), or `undefined`.
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
request.header("authorization");
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### `request.param(name?)`
|
|
357
|
+
|
|
358
|
+
`param(name?: string): string | Record<string, string>`
|
|
359
|
+
|
|
360
|
+
One route parameter by name, or all of them as an object when called with no
|
|
361
|
+
argument.
|
|
362
|
+
|
|
363
|
+
```ts
|
|
364
|
+
request.param("id"); // "42"
|
|
365
|
+
request.param(); // { id: "42" }
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Notes:** the return type is a union — narrow it, or prefer the overloaded
|
|
369
|
+
standalone `param()` helper when you want a precise `string`.
|
|
370
|
+
|
|
371
|
+
#### `request.query(name?)`
|
|
372
|
+
|
|
373
|
+
`query(name?: string): string | undefined | Record<string, string>`
|
|
374
|
+
|
|
375
|
+
One query-string value, or the whole query object with no argument.
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
request.query("q"); // "keel" | undefined
|
|
379
|
+
request.query(); // { q: "keel", page: "2" }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### `request.json<T>()`
|
|
383
|
+
|
|
384
|
+
`json<T = unknown>(): Promise<T>`
|
|
385
|
+
|
|
386
|
+
The parsed JSON body, typed as `T`.
|
|
387
|
+
|
|
388
|
+
```ts
|
|
389
|
+
const body = await request.json<{ email: string }>();
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Notes:** rejects if the body isn't valid JSON. For a query+body merge instead,
|
|
393
|
+
use `request.all()`.
|
|
394
|
+
|
|
395
|
+
#### `request.text()` · `request.arrayBuffer()` · `request.blob()`
|
|
396
|
+
|
|
397
|
+
`text(): Promise<string>` · `arrayBuffer(): Promise<ArrayBuffer>` · `blob(): Promise<Blob>`
|
|
398
|
+
|
|
399
|
+
The raw request body, for content types `json()`/`all()` don't handle — parse it
|
|
400
|
+
yourself.
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
const xml = await request.text(); // XML, CSV, plain text
|
|
404
|
+
const bytes = await request.arrayBuffer(); // protobuf, msgpack, binary
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Notes:** thin passes to the underlying Hono request, which caches the body, so
|
|
408
|
+
these compose with each other (the body isn't re-read).
|
|
409
|
+
|
|
410
|
+
#### `request.raw`
|
|
411
|
+
|
|
412
|
+
`get raw(): Request`
|
|
413
|
+
|
|
414
|
+
The underlying web `Request`.
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
request.raw.signal; // AbortSignal, streaming body, etc.
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### `request.route`
|
|
421
|
+
|
|
422
|
+
`get route(): { name?: string; pattern?: string; methods?: string[] } | undefined`
|
|
423
|
+
|
|
424
|
+
The matched route descriptor the kernel stashed on the context.
|
|
425
|
+
|
|
426
|
+
```ts
|
|
427
|
+
request.route?.name; // "users.show"
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Notes:** `undefined` if no named route matched (or the kernel didn't set it).
|
|
431
|
+
|
|
432
|
+
#### `request.routeIs(name)`
|
|
433
|
+
|
|
434
|
+
`routeIs(name: string): boolean`
|
|
435
|
+
|
|
436
|
+
Whether the matched route has the given name.
|
|
437
|
+
|
|
438
|
+
```ts
|
|
439
|
+
if (request.routeIs("users.show")) highlightNav();
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
#### `request.subdomain(name)`
|
|
443
|
+
|
|
444
|
+
`subdomain(name: string): string | undefined`
|
|
445
|
+
|
|
446
|
+
A subdomain parameter captured from a domain-bound route.
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
request.subdomain("tenant"); // "acme" for acme.example.com
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### `request.cookie(name?)`
|
|
453
|
+
|
|
454
|
+
`cookie(name?: string): string | undefined | Record<string, string>`
|
|
455
|
+
|
|
456
|
+
One request cookie by name, or all cookies with no argument.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
request.cookie("session"); // "abc123" | undefined
|
|
460
|
+
request.cookie(); // { session: "abc123" }
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### `request.ip()`
|
|
464
|
+
|
|
465
|
+
`ip(): string | undefined`
|
|
466
|
+
|
|
467
|
+
The client IP, from `X-Forwarded-For` (first hop) then `X-Real-IP`.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
request.ip(); // "203.0.113.7"
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Notes:** trusts proxy headers — only reliable behind a proxy you control.
|
|
474
|
+
|
|
475
|
+
#### `request.ips()`
|
|
476
|
+
|
|
477
|
+
`ips(): string[]`
|
|
478
|
+
|
|
479
|
+
The full `X-Forwarded-For` chain, client first.
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
request.ips(); // ["203.0.113.7", "10.0.0.1"]
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Notes:** empty array when there's no `X-Forwarded-For`.
|
|
486
|
+
|
|
487
|
+
#### `request.hasBody()`
|
|
488
|
+
|
|
489
|
+
`hasBody(): boolean`
|
|
490
|
+
|
|
491
|
+
True if the request declares a body (has `Content-Length` or
|
|
492
|
+
`Transfer-Encoding`).
|
|
493
|
+
|
|
494
|
+
```ts
|
|
495
|
+
if (request.hasBody()) await request.all();
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
#### `request.headers()`
|
|
499
|
+
|
|
500
|
+
`headers(): Record<string, string>`
|
|
501
|
+
|
|
502
|
+
All request headers as a plain object (names lower-cased by the runtime).
|
|
503
|
+
|
|
504
|
+
```ts
|
|
505
|
+
request.headers(); // { "content-type": "application/json", … }
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### `request.all()`
|
|
509
|
+
|
|
510
|
+
`all(): Promise<Record<string, unknown>>`
|
|
511
|
+
|
|
512
|
+
The query string merged with the parsed body (body wins on key collisions).
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
const input = await request.all(); // { …query, …body }
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
**Notes:** async. Handles JSON and form bodies; drops file fields; swallows a
|
|
519
|
+
missing/invalid body. Backs `input`/`only`/`except`.
|
|
520
|
+
|
|
521
|
+
#### `request.input(key, fallback?)`
|
|
522
|
+
|
|
523
|
+
`input<T = unknown>(key: string, fallback?: T): Promise<T>`
|
|
524
|
+
|
|
525
|
+
A single value from `all()`, with an optional fallback when the key is absent.
|
|
526
|
+
|
|
527
|
+
```ts
|
|
528
|
+
const email = await request.input("email");
|
|
529
|
+
const page = await request.input("page", 1); // T inferred as number
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
**Notes:** the fallback only applies when the key is missing entirely — a
|
|
533
|
+
present-but-empty value is returned as-is.
|
|
534
|
+
|
|
535
|
+
#### `request.only(keys)`
|
|
536
|
+
|
|
537
|
+
`only(keys: string[]): Promise<Record<string, unknown>>`
|
|
538
|
+
|
|
539
|
+
Just the named inputs from `all()`.
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
await request.only(["email", "name"]);
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
**Notes:** keys not present are omitted (not set to `undefined`).
|
|
546
|
+
|
|
547
|
+
#### `request.except(keys)`
|
|
548
|
+
|
|
549
|
+
`except(keys: string[]): Promise<Record<string, unknown>>`
|
|
550
|
+
|
|
551
|
+
Every input except the named ones.
|
|
552
|
+
|
|
553
|
+
```ts
|
|
554
|
+
await request.except(["password", "_csrf"]);
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
#### `request.file(name)`
|
|
558
|
+
|
|
559
|
+
`file(name: string): Promise<File | undefined>`
|
|
560
|
+
|
|
561
|
+
One uploaded file by field name, as a web `File`.
|
|
562
|
+
|
|
563
|
+
```ts
|
|
564
|
+
const avatar = await request.file("avatar");
|
|
565
|
+
if (avatar) await store(await avatar.arrayBuffer());
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Notes:** `undefined` if the field is absent or wasn't a file.
|
|
569
|
+
|
|
570
|
+
#### `request.files(name)`
|
|
571
|
+
|
|
572
|
+
`files(name: string): Promise<File[]>`
|
|
573
|
+
|
|
574
|
+
All uploaded files for a repeated field name.
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
const docs = await request.files("docs"); // File[]
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Notes:** empty array when there are none; non-file values are filtered out.
|
|
581
|
+
|
|
582
|
+
#### `request.allFiles()`
|
|
583
|
+
|
|
584
|
+
`allFiles(): Promise<Record<string, File | File[]>>`
|
|
585
|
+
|
|
586
|
+
Every uploaded file, grouped by field name.
|
|
587
|
+
|
|
588
|
+
```ts
|
|
589
|
+
const files = await request.allFiles(); // { avatar: File, docs: File[] }
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**Notes:** a field with one file maps to a lone `File`; repeated fields map to
|
|
593
|
+
`File[]`.
|
|
594
|
+
|
|
595
|
+
#### `request.accepts(types)`
|
|
596
|
+
|
|
597
|
+
`accepts(types: string[]): string | null`
|
|
598
|
+
|
|
599
|
+
The best of the offered content types per the `Accept` header, or `null`.
|
|
600
|
+
|
|
601
|
+
```ts
|
|
602
|
+
switch (request.accepts(["application/json", "text/html"])) {
|
|
603
|
+
case "application/json": return json(data);
|
|
604
|
+
case "text/html": return html(page);
|
|
605
|
+
default: return response.abort("Not acceptable", 406);
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
**Notes:** honors `*/*`/`*` (returns the first offered). `null` when nothing
|
|
610
|
+
matches.
|
|
611
|
+
|
|
612
|
+
#### `request.types()`
|
|
613
|
+
|
|
614
|
+
`types(): string[]`
|
|
615
|
+
|
|
616
|
+
Accepted content types, ordered by q-weight preference.
|
|
617
|
+
|
|
618
|
+
```ts
|
|
619
|
+
request.types(); // ["text/html", "application/json"]
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### `request.language(languages)`
|
|
623
|
+
|
|
624
|
+
`language(languages: string[]): string | null`
|
|
625
|
+
|
|
626
|
+
The best of the offered languages per `Accept-Language`, or `null`.
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
request.language(["en", "fr"]); // "fr"
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
#### `request.languages()`
|
|
633
|
+
|
|
634
|
+
`languages(): string[]`
|
|
635
|
+
|
|
636
|
+
Accepted languages, ordered by preference.
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
request.languages(); // ["fr", "en"]
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### `request.encoding(encodings)` · `request.encodings()`
|
|
643
|
+
|
|
644
|
+
`encoding(encodings: string[]): string | null` · `encodings(): string[]`
|
|
645
|
+
|
|
646
|
+
Negotiate the response's content encoding against `Accept-Encoding` — same
|
|
647
|
+
q-weight and `*` rules as `accepts`.
|
|
648
|
+
|
|
649
|
+
```ts
|
|
650
|
+
request.encoding(["br", "gzip"]); // "br"
|
|
651
|
+
request.encodings(); // ["br", "gzip", "identity"]
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
#### `request.charset(charsets)` · `request.charsets()`
|
|
655
|
+
|
|
656
|
+
`charset(charsets: string[]): string | null` · `charsets(): string[]`
|
|
657
|
+
|
|
658
|
+
Negotiate the response charset against `Accept-Charset`.
|
|
659
|
+
|
|
660
|
+
```ts
|
|
661
|
+
request.charset(["utf-8", "iso-8859-1"]); // "utf-8"
|
|
662
|
+
request.charsets(); // ["utf-8"]
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### `response`
|
|
666
|
+
|
|
667
|
+
The flat response accessor — a singleton object mirroring `request`. Mutators
|
|
668
|
+
return `response` (chainable); terminals return a `Response`.
|
|
669
|
+
|
|
670
|
+
#### `response.json(data, status?)`
|
|
671
|
+
|
|
672
|
+
`json(data: unknown, status?: number): Response`
|
|
673
|
+
|
|
674
|
+
Same as the standalone `json()`, but reads nicely after chained mutators.
|
|
675
|
+
|
|
676
|
+
```ts
|
|
677
|
+
response.status(201).json(created);
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
#### `response.text(body, status?)`
|
|
681
|
+
|
|
682
|
+
`text(body: string, status?: number): Response`
|
|
683
|
+
|
|
684
|
+
A plain-text response.
|
|
685
|
+
|
|
686
|
+
```ts
|
|
687
|
+
response.text("pong");
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
#### `response.html(body, status?)`
|
|
691
|
+
|
|
692
|
+
`html(body: string, status?: number): Response`
|
|
693
|
+
|
|
694
|
+
An HTML response.
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
response.html("<h1>Hi</h1>");
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
#### `response.redirect(location, status?)`
|
|
701
|
+
|
|
702
|
+
`redirect(location: string, status?: number): Response`
|
|
703
|
+
|
|
704
|
+
A redirect response.
|
|
705
|
+
|
|
706
|
+
```ts
|
|
707
|
+
response.cookie("flash", "saved").redirect("/");
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
Passing `"back"` as the location bounces to the `Referer` header, or `"/"` when
|
|
711
|
+
there isn't one — see `response.back` for a version with a custom fallback.
|
|
712
|
+
|
|
713
|
+
#### `response.back(fallback?, status?)`
|
|
714
|
+
|
|
715
|
+
`back(fallback = "/", status?: number): Response`
|
|
716
|
+
|
|
717
|
+
Redirects to the request's `Referer` header, falling back to `fallback` (default
|
|
718
|
+
`"/"`) when the header is absent. Handy for "return to where you came from" flows
|
|
719
|
+
after a form post.
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
response.abortUnless(ok, "Nope");
|
|
723
|
+
return response.back("/dashboard");
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
#### `response.send(data, status?)`
|
|
727
|
+
|
|
728
|
+
`send(data: unknown, status?: number): Response`
|
|
729
|
+
|
|
730
|
+
Sends a value — a non-null object becomes JSON, everything else becomes text.
|
|
731
|
+
|
|
732
|
+
```ts
|
|
733
|
+
response.send({ ok: true }); // JSON
|
|
734
|
+
response.send("pong"); // text
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
**Notes:** `null` is treated as non-object, so it's stringified to text
|
|
738
|
+
(`"null"`); wrap it in an object if you want JSON `null`.
|
|
739
|
+
|
|
740
|
+
#### `response.status(code)`
|
|
741
|
+
|
|
742
|
+
`status(code: number): ResponseHelper`
|
|
743
|
+
|
|
744
|
+
Sets the response status. Chainable.
|
|
745
|
+
|
|
746
|
+
```ts
|
|
747
|
+
response.status(202).json({ queued: true });
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### `response.header(name, value)`
|
|
751
|
+
|
|
752
|
+
`header(name: string, value: string): ResponseHelper`
|
|
753
|
+
|
|
754
|
+
Sets a response header. Chainable.
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
response.header("x-total", "42").json(rows);
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
#### `response.headers(map)`
|
|
761
|
+
|
|
762
|
+
`headers(map: Record<string, string>): ResponseHelper`
|
|
763
|
+
|
|
764
|
+
Sets several response headers at once. Chainable.
|
|
765
|
+
|
|
766
|
+
```ts
|
|
767
|
+
response.headers({ "x-total": "42", "cache-control": "no-store" });
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
#### `response.getHeader(name)` / `response.hasHeader(name)`
|
|
771
|
+
|
|
772
|
+
`getHeader(name: string): string | null`
|
|
773
|
+
`hasHeader(name: string): boolean`
|
|
774
|
+
|
|
775
|
+
Read a response header set so far — useful in middleware after `await next()`, to
|
|
776
|
+
inspect what a handler set.
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
kernel.use(async (c, next) => {
|
|
780
|
+
await next();
|
|
781
|
+
if (!response.hasHeader("cache-control")) response.header("cache-control", "no-store");
|
|
782
|
+
});
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
#### `response.type(mime)`
|
|
786
|
+
|
|
787
|
+
`type(mime: string): ResponseHelper`
|
|
788
|
+
|
|
789
|
+
Sets the `Content-Type`. Chainable.
|
|
790
|
+
|
|
791
|
+
```ts
|
|
792
|
+
response.type("text/csv").send(csv);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
#### `response.attachment(filename?)`
|
|
796
|
+
|
|
797
|
+
`attachment(filename?: string): ResponseHelper`
|
|
798
|
+
|
|
799
|
+
Marks the response as a downloadable attachment via `Content-Disposition`.
|
|
800
|
+
Chainable. With no argument it sets a bare `attachment`; with a filename it adds
|
|
801
|
+
both a quoted ASCII `filename` and an RFC 5987 `filename*` so non-ASCII names
|
|
802
|
+
survive. Pair it with `type()` to set the download's content type.
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
response.attachment("report.csv").type("text/csv").send(csv);
|
|
806
|
+
// Content-Disposition: attachment; filename="report.csv"; filename*=UTF-8''report.csv
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### `response.append(name, value)`
|
|
810
|
+
|
|
811
|
+
`append(name: string, value: string): ResponseHelper`
|
|
812
|
+
|
|
813
|
+
Appends to a (possibly multi-value) header rather than replacing it. Chainable.
|
|
814
|
+
|
|
815
|
+
```ts
|
|
816
|
+
response.append("vary", "accept").append("vary", "accept-language");
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
#### `response.removeHeader(name)`
|
|
820
|
+
|
|
821
|
+
`removeHeader(name: string): ResponseHelper`
|
|
822
|
+
|
|
823
|
+
Removes a response header. Chainable.
|
|
824
|
+
|
|
825
|
+
```ts
|
|
826
|
+
response.removeHeader("x-powered-by");
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
#### `response.cookie(name, value, options?)`
|
|
830
|
+
|
|
831
|
+
`cookie(name: string, value: string, options?: CookieOptions): ResponseHelper`
|
|
832
|
+
|
|
833
|
+
Queues a `Set-Cookie`. Chainable.
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
response.cookie("session", token, { httpOnly: true, maxAge: 3600 });
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
**Notes:** `options` is Hono's cookie option bag (`httpOnly`, `secure`,
|
|
840
|
+
`sameSite`, `maxAge`, `path`, `domain`, …).
|
|
841
|
+
|
|
842
|
+
#### `response.clearCookie(name, options?)`
|
|
843
|
+
|
|
844
|
+
`clearCookie(name: string, options?: CookieOptions): ResponseHelper`
|
|
845
|
+
|
|
846
|
+
Clears a cookie (queues an expired `Set-Cookie`). Chainable.
|
|
847
|
+
|
|
848
|
+
```ts
|
|
849
|
+
response.clearCookie("session");
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
**Notes:** pass the same `path`/`domain` you set the cookie with, or the browser
|
|
853
|
+
won't match it.
|
|
854
|
+
|
|
855
|
+
#### `response.abort(message, status?)`
|
|
856
|
+
|
|
857
|
+
`abort(message: string, status?: number): never`
|
|
858
|
+
|
|
859
|
+
Throws an `HttpException` to end the request.
|
|
860
|
+
|
|
861
|
+
```ts
|
|
862
|
+
response.abort("Not found", 404);
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
**Notes:** default status `400`. Return type is `never`, so TypeScript treats
|
|
866
|
+
everything after it as unreachable. Rendered by the kernel (see
|
|
867
|
+
[Errors](./errors.md)).
|
|
868
|
+
|
|
869
|
+
#### `response.abortIf(condition, message, status?)`
|
|
870
|
+
|
|
871
|
+
`abortIf(condition: unknown, message: string, status?: number): void`
|
|
872
|
+
|
|
873
|
+
Aborts only if `condition` is truthy.
|
|
874
|
+
|
|
875
|
+
```ts
|
|
876
|
+
response.abortIf(!user, "Not found", 404);
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
**Notes:** default status `400`. Doesn't narrow types (return is `void`, not a
|
|
880
|
+
type guard).
|
|
881
|
+
|
|
882
|
+
#### `response.abortUnless(condition, message, status?)`
|
|
883
|
+
|
|
884
|
+
`abortUnless(condition: unknown, message: string, status?: number): void`
|
|
885
|
+
|
|
886
|
+
Aborts unless `condition` is truthy.
|
|
887
|
+
|
|
888
|
+
```ts
|
|
889
|
+
response.abortUnless(user?.isAdmin, "Forbidden", 403);
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
**Notes:** default status `400`.
|
|
893
|
+
|
|
894
|
+
### Standalone shortcuts
|
|
895
|
+
|
|
896
|
+
Flat helpers for terse handlers — they resolve the same request as `request`.
|
|
897
|
+
|
|
898
|
+
#### `param(name?)`
|
|
899
|
+
|
|
900
|
+
`param(): Record<string, string>`
|
|
901
|
+
`param(name: string): string`
|
|
902
|
+
|
|
903
|
+
One route parameter (typed `string`) or all of them.
|
|
904
|
+
|
|
905
|
+
```ts
|
|
906
|
+
import { param } from "@shaferllc/keel/core";
|
|
907
|
+
|
|
908
|
+
param("id"); // string
|
|
909
|
+
param(); // Record<string, string>
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
**Notes:** overloaded, so `param("id")` is precisely `string` — unlike
|
|
913
|
+
`request.param`, which returns the union.
|
|
914
|
+
|
|
915
|
+
#### `query(name?)`
|
|
916
|
+
|
|
917
|
+
`query(): Record<string, string>`
|
|
918
|
+
`query(name: string): string | undefined`
|
|
919
|
+
|
|
920
|
+
One query value or the whole query object.
|
|
921
|
+
|
|
922
|
+
```ts
|
|
923
|
+
import { query } from "@shaferllc/keel/core";
|
|
924
|
+
|
|
925
|
+
query("q"); // string | undefined
|
|
926
|
+
query(); // Record<string, string>
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
#### `header(name)`
|
|
930
|
+
|
|
931
|
+
`header(name: string): string | undefined`
|
|
932
|
+
|
|
933
|
+
A single request header.
|
|
934
|
+
|
|
935
|
+
```ts
|
|
936
|
+
import { header } from "@shaferllc/keel/core";
|
|
937
|
+
|
|
938
|
+
header("authorization");
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### `body<T>()`
|
|
942
|
+
|
|
943
|
+
`body<T = unknown>(): Promise<T>`
|
|
944
|
+
|
|
945
|
+
The parsed JSON body — the standalone twin of `request.json<T>()`.
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
import { body } from "@shaferllc/keel/core";
|
|
949
|
+
|
|
950
|
+
const data = await body<{ email: string }>();
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
**Notes:** rejects on invalid JSON.
|