| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- // src/context.ts
- import { HonoRequest } from "./request.js";
- import { HtmlEscapedCallbackPhase, resolveCallback } from "./utils/html.js";
- var TEXT_PLAIN = "text/plain; charset=UTF-8";
- var setDefaultContentType = (contentType, headers) => {
- return {
- "Content-Type": contentType,
- ...headers
- };
- };
- var 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')
- *
- * 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()
- * ```
- */
- // c.var.propName is a read-only
- get var() {
- if (!this.#var) {
- return {};
- }
- return Object.fromEntries(this.#var);
- }
- #newResponse(data, arg, headers) {
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers();
- if (typeof arg === "object" && "headers" in arg) {
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
- for (const [key, value] of argHeaders) {
- if (key.toLowerCase() === "set-cookie") {
- responseHeaders.append(key, value);
- } else {
- responseHeaders.set(key, value);
- }
- }
- }
- if (headers) {
- for (const [k, v] of Object.entries(headers)) {
- 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 });
- }
- 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 = (html2) => this.#newResponse(html2, 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",
- // Multibyes should be encoded
- // eslint-disable-next-line no-control-regex
- !/[^\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);
- };
- };
- export {
- Context,
- TEXT_PLAIN
- };
|