- Add debounced state updates for title and content (500ms delay) - Immediate UI updates with delayed history saving - Prevent one-letter-per-undo issue - Add cleanup for debounce timers on unmount
302 lines
7.5 KiB
JavaScript
302 lines
7.5 KiB
JavaScript
// src/request.ts
|
|
import { HTTPException } from "./http-exception.js";
|
|
import { GET_MATCH_RESULT } from "./request/constants.js";
|
|
import { parseBody } from "./utils/body.js";
|
|
import { decodeURIComponent_, getQueryParam, getQueryParams, tryDecode } from "./utils/url.js";
|
|
var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
|
|
var HonoRequest = class {
|
|
/**
|
|
* `.raw` can get the raw Request object.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#raw}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* // For Cloudflare Workers
|
|
* app.post('/', async (c) => {
|
|
* const metadata = c.req.raw.cf?.hostMetadata?
|
|
* ...
|
|
* })
|
|
* ```
|
|
*/
|
|
raw;
|
|
#validatedData;
|
|
// Short name of validatedData
|
|
#matchResult;
|
|
routeIndex = 0;
|
|
/**
|
|
* `.path` can get the pathname of the request.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#path}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const pathname = c.req.path // `/about/me`
|
|
* })
|
|
* ```
|
|
*/
|
|
path;
|
|
bodyCache = {};
|
|
constructor(request, path = "/", matchResult = [[]]) {
|
|
this.raw = request;
|
|
this.path = path;
|
|
this.#matchResult = matchResult;
|
|
this.#validatedData = {};
|
|
}
|
|
param(key) {
|
|
return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
|
|
}
|
|
#getDecodedParam(key) {
|
|
const paramKey = this.#matchResult[0][this.routeIndex][1][key];
|
|
const param = this.#getParamValue(paramKey);
|
|
return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
|
|
}
|
|
#getAllDecodedParams() {
|
|
const decoded = {};
|
|
const keys = Object.keys(this.#matchResult[0][this.routeIndex][1]);
|
|
for (const key of keys) {
|
|
const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
|
|
if (value !== void 0) {
|
|
decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
|
|
}
|
|
}
|
|
return decoded;
|
|
}
|
|
#getParamValue(paramKey) {
|
|
return this.#matchResult[1] ? this.#matchResult[1][paramKey] : paramKey;
|
|
}
|
|
query(key) {
|
|
return getQueryParam(this.url, key);
|
|
}
|
|
queries(key) {
|
|
return getQueryParams(this.url, key);
|
|
}
|
|
header(name) {
|
|
if (name) {
|
|
return this.raw.headers.get(name) ?? void 0;
|
|
}
|
|
const headerData = {};
|
|
this.raw.headers.forEach((value, key) => {
|
|
headerData[key] = value;
|
|
});
|
|
return headerData;
|
|
}
|
|
async parseBody(options) {
|
|
return this.bodyCache.parsedBody ??= await parseBody(this, options);
|
|
}
|
|
#cachedBody = (key) => {
|
|
const { bodyCache, raw } = this;
|
|
const cachedBody = bodyCache[key];
|
|
if (cachedBody) {
|
|
return cachedBody;
|
|
}
|
|
const anyCachedKey = Object.keys(bodyCache)[0];
|
|
if (anyCachedKey) {
|
|
return bodyCache[anyCachedKey].then((body) => {
|
|
if (anyCachedKey === "json") {
|
|
body = JSON.stringify(body);
|
|
}
|
|
return new Response(body)[key]();
|
|
});
|
|
}
|
|
return bodyCache[key] = raw[key]();
|
|
};
|
|
/**
|
|
* `.json()` can parse Request body of type `application/json`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#json}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.json()
|
|
* })
|
|
* ```
|
|
*/
|
|
json() {
|
|
return this.#cachedBody("text").then((text) => JSON.parse(text));
|
|
}
|
|
/**
|
|
* `.text()` can parse Request body of type `text/plain`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#text}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.text()
|
|
* })
|
|
* ```
|
|
*/
|
|
text() {
|
|
return this.#cachedBody("text");
|
|
}
|
|
/**
|
|
* `.arrayBuffer()` parse Request body as an `ArrayBuffer`
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#arraybuffer}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.arrayBuffer()
|
|
* })
|
|
* ```
|
|
*/
|
|
arrayBuffer() {
|
|
return this.#cachedBody("arrayBuffer");
|
|
}
|
|
/**
|
|
* Parses the request body as a `Blob`.
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.blob();
|
|
* });
|
|
* ```
|
|
* @see https://hono.dev/docs/api/request#blob
|
|
*/
|
|
blob() {
|
|
return this.#cachedBody("blob");
|
|
}
|
|
/**
|
|
* Parses the request body as `FormData`.
|
|
* @example
|
|
* ```ts
|
|
* app.post('/entry', async (c) => {
|
|
* const body = await c.req.formData();
|
|
* });
|
|
* ```
|
|
* @see https://hono.dev/docs/api/request#formdata
|
|
*/
|
|
formData() {
|
|
return this.#cachedBody("formData");
|
|
}
|
|
/**
|
|
* Adds validated data to the request.
|
|
*
|
|
* @param target - The target of the validation.
|
|
* @param data - The validated data to add.
|
|
*/
|
|
addValidatedData(target, data) {
|
|
this.#validatedData[target] = data;
|
|
}
|
|
valid(target) {
|
|
return this.#validatedData[target];
|
|
}
|
|
/**
|
|
* `.url()` can get the request url strings.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#url}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const url = c.req.url // `http://localhost:8787/about/me`
|
|
* ...
|
|
* })
|
|
* ```
|
|
*/
|
|
get url() {
|
|
return this.raw.url;
|
|
}
|
|
/**
|
|
* `.method()` can get the method name of the request.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#method}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/about/me', (c) => {
|
|
* const method = c.req.method // `GET`
|
|
* })
|
|
* ```
|
|
*/
|
|
get method() {
|
|
return this.raw.method;
|
|
}
|
|
get [GET_MATCH_RESULT]() {
|
|
return this.#matchResult;
|
|
}
|
|
/**
|
|
* `.matchedRoutes()` can return a matched route in the handler
|
|
*
|
|
* @deprecated
|
|
*
|
|
* Use matchedRoutes helper defined in "hono/route" instead.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#matchedroutes}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.use('*', async function logger(c, next) {
|
|
* await next()
|
|
* c.req.matchedRoutes.forEach(({ handler, method, path }, i) => {
|
|
* const name = handler.name || (handler.length < 2 ? '[handler]' : '[middleware]')
|
|
* console.log(
|
|
* method,
|
|
* ' ',
|
|
* path,
|
|
* ' '.repeat(Math.max(10 - path.length, 0)),
|
|
* name,
|
|
* i === c.req.routeIndex ? '<- respond from here' : ''
|
|
* )
|
|
* })
|
|
* })
|
|
* ```
|
|
*/
|
|
get matchedRoutes() {
|
|
return this.#matchResult[0].map(([[, route]]) => route);
|
|
}
|
|
/**
|
|
* `routePath()` can retrieve the path registered within the handler
|
|
*
|
|
* @deprecated
|
|
*
|
|
* Use routePath helper defined in "hono/route" instead.
|
|
*
|
|
* @see {@link https://hono.dev/docs/api/request#routepath}
|
|
*
|
|
* @example
|
|
* ```ts
|
|
* app.get('/posts/:id', (c) => {
|
|
* return c.json({ path: c.req.routePath })
|
|
* })
|
|
* ```
|
|
*/
|
|
get routePath() {
|
|
return this.#matchResult[0].map(([[, route]]) => route)[this.routeIndex].path;
|
|
}
|
|
};
|
|
var cloneRawRequest = async (req) => {
|
|
if (!req.raw.bodyUsed) {
|
|
return req.raw.clone();
|
|
}
|
|
const cacheKey = Object.keys(req.bodyCache)[0];
|
|
if (!cacheKey) {
|
|
throw new HTTPException(500, {
|
|
message: "Cannot clone request: body was already consumed and not cached. Please use HonoRequest methods (e.g., req.json(), req.text()) instead of consuming req.raw directly."
|
|
});
|
|
}
|
|
const requestInit = {
|
|
body: await req[cacheKey](),
|
|
cache: req.raw.cache,
|
|
credentials: req.raw.credentials,
|
|
headers: req.header(),
|
|
integrity: req.raw.integrity,
|
|
keepalive: req.raw.keepalive,
|
|
method: req.method,
|
|
mode: req.raw.mode,
|
|
redirect: req.raw.redirect,
|
|
referrer: req.raw.referrer,
|
|
referrerPolicy: req.raw.referrerPolicy,
|
|
signal: req.raw.signal
|
|
};
|
|
return new Request(req.url, requestInit);
|
|
};
|
|
export {
|
|
HonoRequest,
|
|
cloneRawRequest
|
|
};
|