Files
scriptc/tests/fixtures/npm/node_modules/hono/dist/context.js
T
Malte UblandClaude Opus 5.5 dddcbed335 Project zod 4 and @hono/zod-validator values at the declaration boundary
The declaration-typed boundary already reaches zod 4 schemas and hono's
c.req.valid(): zod 4's z.infer and hono's validator Input generics resolve
to plain object types in the declaration world. Three gaps remained:

- Boolean literal types printed as `false` (the TS7 facade leaves
  intrinsicName unset), so `{ ok: true } | { ok: false }` lost its true
  arm and `boolean | undefined` became `undefined | false`. The printer
  reads the literal's value.
- `r.data` after `const r = S.safeParse(x); if (r.success)` never
  crossed: zod 4's result union is not data (its error arm is a class),
  and reads on projectable receivers were skipped on the assumption that
  something else types the receiver. Such reads now cross when the
  receiver is a package call whose type does not project, or a const
  bound to one.
- Reads through bindings annotated with a projected type no longer get a
  redundant cast, and aliases are named only for casts that survive.

New npm cases zod4-boundary and hono-zod-validator run real zod 4.6.7,
hono 4.13.13 and @hono/zod-validator 0.9.1 (vendored file closures) under
Node and natively; decl-boundary.test.ts checks that c.req.valid() is a
checked cast with a path TypeError.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:09:39 -07:00

382 lines
9.9 KiB
JavaScript

import { HonoRequest } from "./request.js";
import { HtmlEscapedCallbackPhase, resolveCallback } from "./utils/html.js";
//#region src/context.ts
const TEXT_PLAIN = "text/plain; charset=UTF-8";
const setDefaultContentType = (contentType, headers) => {
return {
"Content-Type": contentType,
...headers
};
};
const createResponseInstance = (body, init) => new Response(body, init);
var Context = class {
#rawRequest;
#req;
/**
* `.env` can get bindings (environment variables, secrets, KV namespaces, D1 database, R2 bucket etc.) in Cloudflare Workers.
*
* @see {@link https://hono.dev/docs/api/context#env}
*
* @example
* ```ts
* // Environment object for Cloudflare Workers
* app.get('*', async c => {
* const counter = c.env.COUNTER
* })
* ```
*/
env = {};
#var;
finalized = false;
/**
* `.error` can get the error object from the middleware if the Handler throws an error.
*
* @see {@link https://hono.dev/docs/api/context#error}
*
* @example
* ```ts
* app.use('*', async (c, next) => {
* await next()
* if (c.error) {
* // do something...
* }
* })
* ```
*/
error;
#status;
#executionCtx;
#res;
#layout;
#renderer;
#notFoundHandler;
#preparedHeaders;
#matchResult;
#path;
/**
* Creates an instance of the Context class.
*
* @param req - The Request object.
* @param options - Optional configuration options for the context.
*/
constructor(req, options) {
this.#rawRequest = req;
if (options) {
this.#executionCtx = options.executionCtx;
this.env = options.env;
this.#notFoundHandler = options.notFoundHandler;
this.#path = options.path;
this.#matchResult = options.matchResult;
}
}
/**
* `.req` is the instance of {@link HonoRequest}.
*/
get req() {
this.#req ??= new HonoRequest(this.#rawRequest, this.#path, this.#matchResult);
return this.#req;
}
/**
* @see {@link https://hono.dev/docs/api/context#event}
* The FetchEvent associated with the current request.
*
* @throws Will throw an error if the context does not have a FetchEvent.
*/
get event() {
if (this.#executionCtx && "respondWith" in this.#executionCtx) return this.#executionCtx;
else throw Error("This context has no FetchEvent");
}
/**
* @see {@link https://hono.dev/docs/api/context#executionctx}
* The ExecutionContext associated with the current request.
*
* @throws Will throw an error if the context does not have an ExecutionContext.
*/
get executionCtx() {
if (this.#executionCtx) return this.#executionCtx;
else throw Error("This context has no ExecutionContext");
}
/**
* @see {@link https://hono.dev/docs/api/context#res}
* The Response object for the current request.
*/
get res() {
return this.#res ||= createResponseInstance(null, { headers: this.#preparedHeaders ??= new Headers() });
}
/**
* Sets the Response object for the current request.
*
* @param _res - The Response object to set.
*/
set res(_res) {
if (this.#res && _res) {
_res = createResponseInstance(_res.body, _res);
for (const [k, v] of this.#res.headers.entries()) {
if (k === "content-type") continue;
if (k === "set-cookie") {
const cookies = this.#res.headers.getSetCookie();
_res.headers.delete("set-cookie");
for (const cookie of cookies) _res.headers.append("set-cookie", cookie);
} else _res.headers.set(k, v);
}
}
this.#res = _res;
this.finalized = true;
}
/**
* `.render()` can create a response within a layout.
*
* @see {@link https://hono.dev/docs/api/context#render-setrenderer}
*
* @example
* ```ts
* app.get('/', (c) => {
* return c.render('Hello!')
* })
* ```
*/
render = (...args) => {
this.#renderer ??= (content) => this.html(content);
return this.#renderer(...args);
};
/**
* Sets the layout for the response.
*
* @param layout - The layout to set.
* @returns The layout function.
*/
setLayout = (layout) => this.#layout = layout;
/**
* Gets the current layout for the response.
*
* @returns The current layout function.
*/
getLayout = () => this.#layout;
/**
* `.setRenderer()` can set the layout in the custom middleware.
*
* @see {@link https://hono.dev/docs/api/context#render-setrenderer}
*
* @example
* ```tsx
* app.use('*', async (c, next) => {
* c.setRenderer((content) => {
* return c.html(
* <html>
* <body>
* <p>{content}</p>
* </body>
* </html>
* )
* })
* await next()
* })
* ```
*/
setRenderer = (renderer) => {
this.#renderer = renderer;
};
/**
* `.header()` can set headers.
*
* @see {@link https://hono.dev/docs/api/context#header}
*
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
*
* // Append multiple headers using the append option (e.g. Vary)
* c.header('Vary', 'Accept-Encoding', { append: true })
* c.header('Vary', 'User-Agent', { append: true })
*
* return c.body('Thank you for coming')
* })
* ```
*/
header = (name, value, options) => {
if (this.finalized) this.#res = createResponseInstance(this.#res.body, this.#res);
const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers();
if (value === void 0) headers.delete(name);
else if (options?.append) headers.append(name, value);
else headers.set(name, value);
};
status = (status) => {
this.#status = status;
};
/**
* `.set()` can set the value specified by the key.
*
* @see {@link https://hono.dev/docs/api/context#set-get}
*
* @example
* ```ts
* app.use('*', async (c, next) => {
* c.set('message', 'Hono is hot!!')
* await next()
* })
* ```
*/
set = (key, value) => {
this.#var ??= /* @__PURE__ */ new Map();
this.#var.set(key, value);
};
/**
* `.get()` can use the value specified by the key.
*
* @see {@link https://hono.dev/docs/api/context#set-get}
*
* @example
* ```ts
* app.get('/', (c) => {
* const message = c.get('message')
* return c.text(`The message is "${message}"`)
* })
* ```
*/
get = (key) => {
return this.#var ? this.#var.get(key) : void 0;
};
/**
* `.var` can access the value of a variable.
*
* @see {@link https://hono.dev/docs/api/context#var}
*
* @example
* ```ts
* const result = c.var.client.oneMethod()
* ```
*/
get var() {
if (!this.#var) return {};
return Object.fromEntries(this.#var);
}
#newResponse(data, arg, headers) {
let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
if (typeof arg === "object" && arg.headers) {
responseHeaders ??= new Headers();
for (const [key, value] of new Headers(arg.headers)) if (key === "set-cookie") responseHeaders.append(key, value);
else responseHeaders.set(key, value);
}
if (headers) {
if (!responseHeaders) {
let count = 0;
for (const k in headers) if (++count > 1 || typeof headers[k] !== "string") {
responseHeaders = new Headers();
break;
}
}
if (responseHeaders) for (const k in headers) {
const v = headers[k];
if (typeof v === "string") responseHeaders.set(k, v);
else {
responseHeaders.delete(k);
for (const v2 of v) responseHeaders.append(k, v2);
}
}
}
const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
return createResponseInstance(data, {
status,
headers: responseHeaders ?? headers
});
}
newResponse = (...args) => this.#newResponse(...args);
/**
* `.body()` can return the HTTP response.
* You can set headers with `.header()` and set HTTP status code with `.status`.
* This can also be set in `.text()`, `.json()` and so on.
*
* @see {@link https://hono.dev/docs/api/context#body}
*
* @example
* ```ts
* app.get('/welcome', (c) => {
* // Set headers
* c.header('X-Message', 'Hello!')
* c.header('Content-Type', 'text/plain')
* // Set HTTP status code
* c.status(201)
*
* // Return the response body
* return c.body('Thank you for coming')
* })
* ```
*/
body = (data, arg, headers) => this.#newResponse(data, arg, headers);
/**
* `.text()` can render text as `Content-Type:text/plain`.
*
* @see {@link https://hono.dev/docs/api/context#text}
*
* @example
* ```ts
* app.get('/say', (c) => {
* return c.text('Hello!')
* })
* ```
*/
text = (text, arg, headers) => {
return !this.#preparedHeaders && !this.#status && !arg && !headers && !this.finalized ? new Response(text) : this.#newResponse(text, arg, setDefaultContentType(TEXT_PLAIN, headers));
};
/**
* `.json()` can render JSON as `Content-Type:application/json`.
*
* @see {@link https://hono.dev/docs/api/context#json}
*
* @example
* ```ts
* app.get('/api', (c) => {
* return c.json({ message: 'Hello!' })
* })
* ```
*/
json = (object, arg, headers) => {
return this.#newResponse(JSON.stringify(object), arg, setDefaultContentType("application/json", headers));
};
html = (html, arg, headers) => {
const res = (html) => this.#newResponse(html, arg, setDefaultContentType("text/html; charset=UTF-8", headers));
return typeof html === "object" ? resolveCallback(html, HtmlEscapedCallbackPhase.Stringify, false, {}).then(res) : res(html);
};
/**
* `.redirect()` can Redirect, default status code is 302.
*
* @see {@link https://hono.dev/docs/api/context#redirect}
*
* @example
* ```ts
* app.get('/redirect', (c) => {
* return c.redirect('/')
* })
* app.get('/redirect-permanently', (c) => {
* return c.redirect('/', 301)
* })
* ```
*/
redirect = (location, status) => {
const locationString = String(location);
this.header("Location", !/[^\x00-\xFF]/.test(locationString) ? locationString : encodeURI(locationString));
return this.newResponse(null, status ?? 302);
};
/**
* `.notFound()` can return the Not Found Response.
*
* @see {@link https://hono.dev/docs/api/context#notfound}
*
* @example
* ```ts
* app.get('/notfound', (c) => {
* return c.notFound()
* })
* ```
*/
notFound = () => {
this.#notFoundHandler ??= () => createResponseInstance();
return this.#notFoundHandler(this);
};
};
//#endregion
export { Context, TEXT_PLAIN };