From f02320b39631065f1727f13fa8ea92ef07d3e192 Mon Sep 17 00:00:00 2001 From: Rokas Muningis <28229273+muningis@users.noreply.github.com> Date: Sun, 7 Dec 2025 09:27:43 +0200 Subject: [PATCH 1/5] feat(response cache): initial implementation --- packages/response-cache/README.md | 80 +++ packages/response-cache/package.json | 47 ++ packages/response-cache/src/index.ts | 65 ++ packages/response-cache/tsconfig.json | 10 + yarn.lock | 814 +++++++++++++++++++++++++- 5 files changed, 1000 insertions(+), 16 deletions(-) create mode 100644 packages/response-cache/README.md create mode 100644 packages/response-cache/package.json create mode 100644 packages/response-cache/src/index.ts create mode 100644 packages/response-cache/tsconfig.json diff --git a/packages/response-cache/README.md b/packages/response-cache/README.md new file mode 100644 index 000000000..b0842edb2 --- /dev/null +++ b/packages/response-cache/README.md @@ -0,0 +1,80 @@ +# Response Cache for Hono +Response cache for [Hono](https://honojs.dev) with `Bring Your Own` cache store. + +## Usage +### Basic with `in-memory` cache: +```ts +import { Hono } from 'hono' +import { cacheMiddleware } from '@hono/response-cache' + +const cacheStorage = new Map() +const cache = cacheMiddleware({ + store: { + get: (key) => cacheStorage.get(key), + set: (key, value) => { + cacheStorage.set(key, value) + }, + delete: (key) => { + cacheStorage.delete(key) + }, + }, +}) + +const app = new Hono() +app.use('*', cache) +``` + +### Redis (and custom key function) +```ts +import { Hono } from 'hono' +import { cacheMiddleware } from '@hono/response-cache' +import { createClient } from '@redis/client' + +const redisClient = createClient({ + url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, +}) + +const store = { + get: async (key: string) => { + return (await redisClient.get(key)) ?? null + }, + set: async (key: string, value: string) => { + await redisClient.set(key, value) + return + }, + invalidate: async (key: string) => { + await redisClient.del(key) + return + }, +} + +const cache = cacheMiddleware({ + store, + keyFn: (req, c) => `hono_res_cache_${c.req.path}`, +}) + +app.use('*', cache) +``` + +### Add logging +```ts +import { cacheMiddleware } from '@hono/response-cache' + +const cache = cacheMiddleware({ + store, + logging: { + enabled: true, + onHit: (key, c) => console.log(`Cache hit for ${key}`), + onMiss: (key, c) => console.log(`Cache miss for ${key}`), + onError: (key, c) => console.log(`Cache error for ${key}, error:`, error), + }, +}) +``` + +## Author + +Rokas Muningis + +## License + +MIT \ No newline at end of file diff --git a/packages/response-cache/package.json b/packages/response-cache/package.json new file mode 100644 index 000000000..bed18024c --- /dev/null +++ b/packages/response-cache/package.json @@ -0,0 +1,47 @@ +{ + "name": "@hono/redis-cache", + "version": "0.0.0", + "description": "Redis cache for Hono", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist" + ], + "scripts": { + "test": "tsc --noEmit && vitest --run", + "build": "tsc", + "publint": "publint", + "prerelease": "yarn build && yarn test", + "release": "yarn publish" + }, + "license": "MIT", + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/honojs/middleware.git" + }, + "homepage": "https://github.com/honojs/middleware", + "peerDependencies": { + "hono": ">=4.7.0" + }, + "dependencies": { + "hono": ">=4.7.0" + }, + "devDependencies": { + "publint": "^0.2.7", + "tsup": "^8.1.0", + "typescript": "^5.7.3" + } +} diff --git a/packages/response-cache/src/index.ts b/packages/response-cache/src/index.ts new file mode 100644 index 000000000..27c4e8e78 --- /dev/null +++ b/packages/response-cache/src/index.ts @@ -0,0 +1,65 @@ +import type { Context, HonoRequest } from 'hono' +import { createMiddleware } from 'hono/factory' + +type TOrPromise = T | Promise + +interface CacheStore { + get(key: string): TOrPromise + set(key: string, value: string): TOrPromise + invalidate(key: string): TOrPromise +} + +interface CacheMiddlewareOptions { + store: CacheStore; + /** + * The response type to cache. If not provided, the response will be returned as HTML. + * @default 'html' + */ + respond?: 'body' | 'text' | 'json' | 'html'; + /** + * Function to generate a cache key from the request and context. If not provided, the request path will be used. + * @param req - The request object. + * @param c - The context object. + * @returns A string key for the cache. + */ + keyFn?: (req: HonoRequest, c: Context) => string; + logging?: { + enabled?: boolean; + onHit?: (key: string, c: Context) => void; + onMiss?: (key: string, c: Context) => void; + onError?: (key: string, c: Context, error: unknown) => void; + }; +} +const responseCache = ({ store, keyFn, respond = 'html', logging }: CacheMiddlewareOptions) => { + return createMiddleware(async (c, next) => { + const key = keyFn ? keyFn(c.req, c) : c.req.path + const cached = await store.get(key) + try { + if (cached) { + if (logging?.enabled) {logging.onHit?.(key, c)} + if (respond === 'json') { + return c.json(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) + } + else if (respond === 'html') { + return await c.html(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) + } + else if (respond === 'text') { + return c.text(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) + } + else if (respond === 'body') { + return c.body(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) + } + } else { + if (logging?.enabled) {logging.onMiss?.(key, c)} + await next() + const response = await c.res.clone().text() + await store.set(key, JSON.stringify(response)) + } + } catch (error) { + if (logging?.enabled) {logging.onError?.(key, c, error)} + throw error + } + }) +} + +export { responseCache } diff --git a/packages/response-cache/tsconfig.json b/packages/response-cache/tsconfig.json new file mode 100644 index 000000000..80831b3ee --- /dev/null +++ b/packages/response-cache/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + }, + "include": [ + "src/**/*.ts" + ], + } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c0d9291a2..9faa7d8c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1094,6 +1094,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/aix-ppc64@npm:0.27.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm64@npm:0.25.1" @@ -1115,6 +1122,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-arm64@npm:0.27.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm@npm:0.25.1" @@ -1136,6 +1150,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-arm@npm:0.27.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-x64@npm:0.25.1" @@ -1157,6 +1178,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/android-x64@npm:0.27.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-arm64@npm:0.25.1" @@ -1178,6 +1206,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/darwin-arm64@npm:0.27.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-x64@npm:0.25.1" @@ -1199,6 +1234,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/darwin-x64@npm:0.27.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-arm64@npm:0.25.1" @@ -1220,6 +1262,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/freebsd-arm64@npm:0.27.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-x64@npm:0.25.1" @@ -1241,6 +1290,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/freebsd-x64@npm:0.27.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm64@npm:0.25.1" @@ -1262,6 +1318,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-arm64@npm:0.27.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm@npm:0.25.1" @@ -1283,6 +1346,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-arm@npm:0.27.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ia32@npm:0.25.1" @@ -1304,6 +1374,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-ia32@npm:0.27.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-loong64@npm:0.25.1" @@ -1325,6 +1402,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-loong64@npm:0.27.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-mips64el@npm:0.25.1" @@ -1346,6 +1430,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-mips64el@npm:0.27.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ppc64@npm:0.25.1" @@ -1367,6 +1458,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-ppc64@npm:0.27.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-riscv64@npm:0.25.1" @@ -1388,6 +1486,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-riscv64@npm:0.27.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-s390x@npm:0.25.1" @@ -1409,6 +1514,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-s390x@npm:0.27.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-x64@npm:0.25.1" @@ -1430,6 +1542,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/linux-x64@npm:0.27.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-arm64@npm:0.25.1" @@ -1451,6 +1570,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/netbsd-arm64@npm:0.27.1" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-x64@npm:0.25.1" @@ -1472,6 +1598,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/netbsd-x64@npm:0.27.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-arm64@npm:0.25.1" @@ -1493,6 +1626,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openbsd-arm64@npm:0.27.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-x64@npm:0.25.1" @@ -1514,6 +1654,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openbsd-x64@npm:0.27.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openharmony-arm64@npm:0.25.10": version: 0.25.10 resolution: "@esbuild/openharmony-arm64@npm:0.25.10" @@ -1521,6 +1668,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openharmony-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/openharmony-arm64@npm:0.27.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/sunos-x64@npm:0.25.1" @@ -1542,6 +1696,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/sunos-x64@npm:0.27.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-arm64@npm:0.25.1" @@ -1563,6 +1724,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-arm64@npm:0.27.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-ia32@npm:0.25.1" @@ -1584,6 +1752,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-ia32@npm:0.27.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-x64@npm:0.25.1" @@ -1605,6 +1780,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.1": + version: 0.27.1 + resolution: "@esbuild/win32-x64@npm:0.27.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.1.2, @eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0, @eslint-community/eslint-utils@npm:^4.4.1": version: 4.5.1 resolution: "@eslint-community/eslint-utils@npm:4.5.1" @@ -2256,6 +2438,19 @@ __metadata: languageName: unknown linkType: soft +"@hono/redis-cache@workspace:packages/response-cache": + version: 0.0.0-use.local + resolution: "@hono/redis-cache@workspace:packages/response-cache" + dependencies: + hono: "npm:>=4.7.0" + publint: "npm:^0.2.7" + tsup: "npm:^8.1.0" + typescript: "npm:^5.7.3" + peerDependencies: + hono: ">=4.7.0" + languageName: unknown + linkType: soft + "@hono/sentry@workspace:packages/sentry": version: 0.0.0-use.local resolution: "@hono/sentry@workspace:packages/sentry" @@ -3238,7 +3433,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/gen-mapping@npm:^0.3.12": +"@jridgewell/gen-mapping@npm:^0.3.12, @jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.13 resolution: "@jridgewell/gen-mapping@npm:0.3.13" dependencies: @@ -4163,6 +4358,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-android-arm64@npm:4.43.0" @@ -4184,6 +4386,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-android-arm64@npm:4.53.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-darwin-arm64@npm:4.43.0" @@ -4205,6 +4414,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-darwin-x64@npm:4.43.0" @@ -4226,6 +4442,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-arm64@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-freebsd-arm64@npm:4.43.0" @@ -4247,6 +4470,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-freebsd-x64@npm:4.43.0" @@ -4268,6 +4498,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.43.0" @@ -4289,6 +4526,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.43.0" @@ -4310,6 +4554,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.43.0" @@ -4331,6 +4582,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.43.0" @@ -4352,6 +4610,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-loong64-gnu@npm:4.52.4": version: 4.52.4 resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.52.4" @@ -4359,6 +4624,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.43.0" @@ -4394,6 +4666,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.43.0" @@ -4415,6 +4694,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-musl@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.43.0" @@ -4436,6 +4722,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.43.0" @@ -4457,6 +4750,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.43.0" @@ -4478,6 +4778,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-linux-x64-musl@npm:4.43.0" @@ -4499,6 +4806,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-openharmony-arm64@npm:4.50.0": version: 4.50.0 resolution: "@rollup/rollup-openharmony-arm64@npm:4.50.0" @@ -4513,6 +4827,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-openharmony-arm64@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.43.0" @@ -4534,6 +4855,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.43.0" @@ -4555,6 +4883,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-gnu@npm:4.52.4": version: 4.52.4 resolution: "@rollup/rollup-win32-x64-gnu@npm:4.52.4" @@ -4562,6 +4897,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-gnu@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.43.0": version: 4.43.0 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.43.0" @@ -4583,6 +4925,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.53.3": + version: 4.53.3 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@ryoppippi/unplugin-typia@npm:1.2.0": version: 1.2.0 resolution: "@ryoppippi/unplugin-typia@npm:1.2.0" @@ -6126,6 +6475,17 @@ __metadata: languageName: node linkType: hard +"bundle-require@npm:^5.1.0": + version: 5.1.0 + resolution: "bundle-require@npm:5.1.0" + dependencies: + load-tsconfig: "npm:^0.2.3" + peerDependencies: + esbuild: ">=0.18" + checksum: 10c0/8bff9df68eb686f05af952003c78e70ffed2817968f92aebb2af620cc0b7428c8154df761d28f1b38508532204278950624ef86ce63644013dc57660a9d1810f + languageName: node + linkType: hard + "bytes@npm:3.1.2, bytes@npm:^3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" @@ -6677,6 +7037,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^4.0.0": + version: 4.1.1 + resolution: "commander@npm:4.1.1" + checksum: 10c0/84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab + languageName: node + linkType: hard + "commander@npm:^5.1.0": version: 5.1.0 resolution: "commander@npm:5.1.0" @@ -6805,7 +7172,7 @@ __metadata: languageName: node linkType: hard -"consola@npm:^3.2.3, consola@npm:^3.4.2": +"consola@npm:^3.2.3, consola@npm:^3.4.0, consola@npm:^3.4.2": version: 3.4.2 resolution: "consola@npm:3.4.2" checksum: 10c0/7cebe57ecf646ba74b300bcce23bff43034ed6fbec9f7e39c27cee1dc00df8a21cd336b466ad32e304ea70fba04ec9e890c200270de9a526ce021ba8a7e4c11a @@ -7965,6 +8332,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.27.0": + version: 0.27.1 + resolution: "esbuild@npm:0.27.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.1" + "@esbuild/android-arm": "npm:0.27.1" + "@esbuild/android-arm64": "npm:0.27.1" + "@esbuild/android-x64": "npm:0.27.1" + "@esbuild/darwin-arm64": "npm:0.27.1" + "@esbuild/darwin-x64": "npm:0.27.1" + "@esbuild/freebsd-arm64": "npm:0.27.1" + "@esbuild/freebsd-x64": "npm:0.27.1" + "@esbuild/linux-arm": "npm:0.27.1" + "@esbuild/linux-arm64": "npm:0.27.1" + "@esbuild/linux-ia32": "npm:0.27.1" + "@esbuild/linux-loong64": "npm:0.27.1" + "@esbuild/linux-mips64el": "npm:0.27.1" + "@esbuild/linux-ppc64": "npm:0.27.1" + "@esbuild/linux-riscv64": "npm:0.27.1" + "@esbuild/linux-s390x": "npm:0.27.1" + "@esbuild/linux-x64": "npm:0.27.1" + "@esbuild/netbsd-arm64": "npm:0.27.1" + "@esbuild/netbsd-x64": "npm:0.27.1" + "@esbuild/openbsd-arm64": "npm:0.27.1" + "@esbuild/openbsd-x64": "npm:0.27.1" + "@esbuild/openharmony-arm64": "npm:0.27.1" + "@esbuild/sunos-x64": "npm:0.27.1" + "@esbuild/win32-arm64": "npm:0.27.1" + "@esbuild/win32-ia32": "npm:0.27.1" + "@esbuild/win32-x64": "npm:0.27.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/8bfcf13a499a9e7b7da4b68273e12b453c7d7a5e39c944c2e5a4c64a0594d6df1391fc168a5353c22bc94eeae38dd9897199ddbbc4973525b0aae18186e996bd + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -8942,6 +9398,17 @@ __metadata: languageName: node linkType: hard +"fix-dts-default-cjs-exports@npm:^1.0.0": + version: 1.0.1 + resolution: "fix-dts-default-cjs-exports@npm:1.0.1" + dependencies: + magic-string: "npm:^0.30.17" + mlly: "npm:^1.7.4" + rollup: "npm:^4.34.8" + checksum: 10c0/61a3cbe32b6c29df495ef3aded78199fe9dbb52e2801c899fe76d9ca413d3c8c51f79986bac83f8b4b2094ebde883ddcfe47b68ce469806ba13ca6ed4e7cd362 + languageName: node + linkType: hard + "flat-cache@npm:^4.0.0": version: 4.0.1 resolution: "flat-cache@npm:4.0.1" @@ -9073,6 +9540,13 @@ __metadata: languageName: node linkType: hard +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 10c0/444cf1291d997165dfd4c0d58b69f0e4782bfd9149fd72faa4fe299e68e0e93d6db941660b37dd29153bf7186672ececa3b50b7e7249477b03fdf850f287c948 + languageName: node + linkType: hard + "fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" @@ -9305,6 +9779,19 @@ __metadata: languageName: node linkType: hard +"glob@npm:^8.0.1": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: "npm:^1.0.0" + inflight: "npm:^1.0.4" + inherits: "npm:2" + minimatch: "npm:^5.0.1" + once: "npm:^1.3.0" + checksum: 10c0/cb0b5cab17a59c57299376abe5646c7070f8acb89df5595b492dba3bfb43d301a46c01e5695f01154e6553168207cb60d4eaf07d3be4bc3eb9b0457c5c561d0f + languageName: node + linkType: hard + "global-dirs@npm:^3.0.0": version: 3.0.1 resolution: "global-dirs@npm:3.0.1" @@ -9651,6 +10138,13 @@ __metadata: languageName: node linkType: hard +"hono@npm:>=4.7.0": + version: 4.10.7 + resolution: "hono@npm:4.10.7" + checksum: 10c0/11aec63ce11b0d635b7ecf24009ba2b694eac7a8ef8e3b7c1b2c7898dc6b39b7a6de1a8454608aade8c66685b016c0e5483b253d55431210a9116461f1a670ea + languageName: node + linkType: hard + "hono@npm:^4.10.1": version: 4.10.3 resolution: "hono@npm:4.10.3" @@ -9792,6 +10286,15 @@ __metadata: languageName: node linkType: hard +"ignore-walk@npm:^5.0.1": + version: 5.0.1 + resolution: "ignore-walk@npm:5.0.1" + dependencies: + minimatch: "npm:^5.0.1" + checksum: 10c0/0d157a54d6d11af0c3059fdc7679eef3b074e9a663d110a76c72788e2fb5b22087e08b21ab767718187ac3396aca4d0aa6c6473f925b19a74d9a00480ca7a76e + languageName: node + linkType: hard + "ignore@npm:^5.2.0, ignore@npm:^5.3.1, ignore@npm:^5.3.2": version: 5.3.2 resolution: "ignore@npm:5.3.2" @@ -9851,7 +10354,17 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: "npm:^1.3.0" + wrappy: "npm:1" + checksum: 10c0/7faca22584600a9dc5b9fca2cd5feb7135ac8c935449837b315676b4c90aa4f391ec4f42240178244b5a34e8bede1948627fda392ca3191522fc46b34e985ab2 + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:2.0.4, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2 @@ -10369,6 +10882,13 @@ __metadata: languageName: node linkType: hard +"joycon@npm:^3.1.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 10c0/131fb1e98c9065d067fd49b6e685487ac4ad4d254191d7aa2c9e3b90f4e9ca70430c43cad001602bdbdabcf58717d3b5c5b7461c1bd8e39478c8de706b3fe6ae + languageName: node + linkType: hard + "js-cookie@npm:3.0.5": version: 3.0.5 resolution: "js-cookie@npm:3.0.5" @@ -10664,6 +11184,27 @@ __metadata: languageName: node linkType: hard +"lilconfig@npm:^3.1.1": + version: 3.1.3 + resolution: "lilconfig@npm:3.1.3" + checksum: 10c0/f5604e7240c5c275743561442fbc5abf2a84ad94da0f5adc71d25e31fa8483048de3dcedcb7a44112a942fed305fd75841cdf6c9681c7f640c63f1049e9a5dcc + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 10c0/3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d + languageName: node + linkType: hard + +"load-tsconfig@npm:^0.2.3": + version: 0.2.5 + resolution: "load-tsconfig@npm:0.2.5" + checksum: 10c0/bf2823dd26389d3497b6567f07435c5a7a58d9df82e879b0b3892f87d8db26900f84c85bc329ef41c0540c0d6a448d1c23ddc64a80f3ff6838b940f3915a3fcb + languageName: node + linkType: hard + "locate-path@npm:^5.0.0": version: 5.0.0 resolution: "locate-path@npm:5.0.0" @@ -11723,7 +12264,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.1.0": +"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": version: 5.1.6 resolution: "minimatch@npm:5.1.6" dependencies: @@ -12009,7 +12550,7 @@ __metadata: languageName: node linkType: hard -"mz@npm:^2.4.0": +"mz@npm:^2.4.0, mz@npm:^2.7.0": version: 2.7.0 resolution: "mz@npm:2.7.0" dependencies: @@ -12223,6 +12764,15 @@ __metadata: languageName: node linkType: hard +"npm-bundled@npm:^2.0.0": + version: 2.0.1 + resolution: "npm-bundled@npm:2.0.1" + dependencies: + npm-normalize-package-bin: "npm:^2.0.0" + checksum: 10c0/5b2dc1de455d38200e49c6205dee185ce919ea6b608672c693bec8907116bc5686dabcc150347630d351c1c533315fd60a1910ce00bdad6bb204cef016b90b7d + languageName: node + linkType: hard + "npm-install-checks@npm:^6.0.0": version: 6.3.0 resolution: "npm-install-checks@npm:6.3.0" @@ -12232,6 +12782,13 @@ __metadata: languageName: node linkType: hard +"npm-normalize-package-bin@npm:^2.0.0": + version: 2.0.0 + resolution: "npm-normalize-package-bin@npm:2.0.0" + checksum: 10c0/9b5283a2e423124c60fbc14244d36686b59e517d29156eacf9df8d3dc5d5bf4d9444b7669c607567ed2e089bbdbef5a2b3678cbf567284eeff3612da6939514b + languageName: node + linkType: hard + "npm-normalize-package-bin@npm:^3.0.0": version: 3.0.1 resolution: "npm-normalize-package-bin@npm:3.0.1" @@ -12251,6 +12808,20 @@ __metadata: languageName: node linkType: hard +"npm-packlist@npm:^5.1.3": + version: 5.1.3 + resolution: "npm-packlist@npm:5.1.3" + dependencies: + glob: "npm:^8.0.1" + ignore-walk: "npm:^5.0.1" + npm-bundled: "npm:^2.0.0" + npm-normalize-package-bin: "npm:^2.0.0" + bin: + npm-packlist: bin/index.js + checksum: 10c0/a8bea97661b2a7132bc8832d5560da24f823ee5324429bd16eb82b7873557de14641bc3fed8a7611b0d88b9771e59e99e01a9e551a53adb164327ded6128aada + languageName: node + linkType: hard + "npm-pick-manifest@npm:^9.0.0": version: 9.1.0 resolution: "npm-pick-manifest@npm:9.1.0" @@ -12364,7 +12935,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.1, once@npm:^1.4.0": +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -12925,6 +13496,13 @@ __metadata: languageName: node linkType: hard +"pirates@npm:^4.0.1": + version: 4.0.7 + resolution: "pirates@npm:4.0.7" + checksum: 10c0/a51f108dd811beb779d58a76864bbd49e239fa40c7984cd11596c75a121a8cc789f1c8971d8bb15f0dbf9d48b76c05bb62fcbce840f89b688c0fa64b37e8478a + languageName: node + linkType: hard + "pkce-challenge@npm:^5.0.0": version: 5.0.0 resolution: "pkce-challenge@npm:5.0.0" @@ -12990,6 +13568,29 @@ __metadata: languageName: node linkType: hard +"postcss-load-config@npm:^6.0.1": + version: 6.0.1 + resolution: "postcss-load-config@npm:6.0.1" + dependencies: + lilconfig: "npm:^3.1.1" + peerDependencies: + jiti: ">=1.21.0" + postcss: ">=8.0.9" + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + checksum: 10c0/74173a58816dac84e44853f7afbd283f4ef13ca0b6baeba27701214beec33f9e309b128f8102e2b173e8d45ecba45d279a9be94b46bf48d219626aa9b5730848 + languageName: node + linkType: hard + "postcss@npm:^8.5.3, postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" @@ -13246,6 +13847,19 @@ __metadata: languageName: node linkType: hard +"publint@npm:^0.2.7": + version: 0.2.12 + resolution: "publint@npm:0.2.12" + dependencies: + npm-packlist: "npm:^5.1.3" + picocolors: "npm:^1.1.1" + sade: "npm:^1.8.1" + bin: + publint: lib/cli.js + checksum: 10c0/f95b81a39cb5ec63bc5cdc7a8dcf8f8fffe6d21020a13bbf79a9e582d79895537c2a316fa6be9bc5a10d66714c53f1658d5e684509f3d94f6fff44b5a919debe + languageName: node + linkType: hard + "publint@npm:^0.3.14, publint@npm:^0.3.15": version: 0.3.15 resolution: "publint@npm:0.3.15" @@ -13928,6 +14542,87 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.34.8": + version: 4.53.3 + resolution: "rollup@npm:4.53.3" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.53.3" + "@rollup/rollup-android-arm64": "npm:4.53.3" + "@rollup/rollup-darwin-arm64": "npm:4.53.3" + "@rollup/rollup-darwin-x64": "npm:4.53.3" + "@rollup/rollup-freebsd-arm64": "npm:4.53.3" + "@rollup/rollup-freebsd-x64": "npm:4.53.3" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" + "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" + "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" + "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" + "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" + "@rollup/rollup-linux-x64-musl": "npm:4.53.3" + "@rollup/rollup-openharmony-arm64": "npm:4.53.3" + "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" + "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" + "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" + "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/a21305aac72013083bd0dec92162b0f7f24cacf57c876ca601ec76e892895952c9ea592c1c07f23b8c125f7979c2b17f7fb565e386d03ee4c1f0952ac4ab0d75 + languageName: node + linkType: hard + "rollup@npm:^4.34.9": version: 4.52.4 resolution: "rollup@npm:4.52.4" @@ -15128,6 +15823,24 @@ __metadata: languageName: node linkType: hard +"sucrase@npm:^3.35.0": + version: 3.35.1 + resolution: "sucrase@npm:3.35.1" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.2" + commander: "npm:^4.0.0" + lines-and-columns: "npm:^1.1.6" + mz: "npm:^2.7.0" + pirates: "npm:^4.0.1" + tinyglobby: "npm:^0.2.11" + ts-interface-checker: "npm:^0.1.9" + bin: + sucrase: bin/sucrase + sucrase-node: bin/sucrase-node + checksum: 10c0/6fa22329c261371feb9560630d961ad0d0b9c87dce21ea74557c5f3ffbe5c1ee970ea8bcce9962ae9c90c3c47165ffa7dd41865c7414f5d8ea7a40755d612c5c + languageName: node + linkType: hard + "superstatic@npm:^9.2.0": version: 9.2.0 resolution: "superstatic@npm:9.2.0" @@ -15396,6 +16109,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.11, tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.12": version: 0.2.12 resolution: "tinyglobby@npm:0.2.12" @@ -15406,16 +16129,6 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.13, tinyglobby@npm:^0.2.15": - version: 0.2.15 - resolution: "tinyglobby@npm:0.2.15" - dependencies: - fdir: "npm:^6.5.0" - picomatch: "npm:^4.0.3" - checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 - languageName: node - linkType: hard - "tinyglobby@npm:^0.2.14, tinyglobby@npm:^0.2.9": version: 0.2.14 resolution: "tinyglobby@npm:0.2.14" @@ -15564,6 +16277,13 @@ __metadata: languageName: node linkType: hard +"ts-interface-checker@npm:^0.1.9": + version: 0.1.13 + resolution: "ts-interface-checker@npm:0.1.13" + checksum: 10c0/232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 + languageName: node + linkType: hard + "ts-node@npm:^10.9.1": version: 10.9.2 resolution: "ts-node@npm:10.9.2" @@ -15671,6 +16391,48 @@ __metadata: languageName: node linkType: hard +"tsup@npm:^8.1.0": + version: 8.5.1 + resolution: "tsup@npm:8.5.1" + dependencies: + bundle-require: "npm:^5.1.0" + cac: "npm:^6.7.14" + chokidar: "npm:^4.0.3" + consola: "npm:^3.4.0" + debug: "npm:^4.4.0" + esbuild: "npm:^0.27.0" + fix-dts-default-cjs-exports: "npm:^1.0.0" + joycon: "npm:^3.1.1" + picocolors: "npm:^1.1.1" + postcss-load-config: "npm:^6.0.1" + resolve-from: "npm:^5.0.0" + rollup: "npm:^4.34.8" + source-map: "npm:^0.7.6" + sucrase: "npm:^3.35.0" + tinyexec: "npm:^0.3.2" + tinyglobby: "npm:^0.2.11" + tree-kill: "npm:^1.2.2" + peerDependencies: + "@microsoft/api-extractor": ^7.36.0 + "@swc/core": ^1 + postcss: ^8.4.12 + typescript: ">=4.5.0" + peerDependenciesMeta: + "@microsoft/api-extractor": + optional: true + "@swc/core": + optional: true + postcss: + optional: true + typescript: + optional: true + bin: + tsup: dist/cli-default.js + tsup-node: dist/cli-node.js + checksum: 10c0/86b0a5ee5533cf5363431ffaf6a244d06fbc80e3a739f216a121a336b28e663d521bae1d3b2d9ba7d479cb4b5f7dcb5e0722e169d3daf664c78f0d68676fa06a + languageName: node + linkType: hard + "tsyringe@npm:^4.10.0": version: 4.10.0 resolution: "tsyringe@npm:4.10.0" @@ -15870,6 +16632,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^5.7.3": + version: 5.9.3 + resolution: "typescript@npm:5.9.3" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 + languageName: node + linkType: hard + "typescript@npm:^5.8.2": version: 5.8.2 resolution: "typescript@npm:5.8.2" @@ -15900,6 +16672,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": + version: 5.9.3 + resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 + languageName: node + linkType: hard + "typescript@patch:typescript@npm%3A^5.8.2#optional!builtin": version: 5.8.2 resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" From a686aa0dcaac5d9f6f7d5196b7c6417aeb7e1483 Mon Sep 17 00:00:00 2001 From: Rokas Muningis <28229273+muningis@users.noreply.github.com> Date: Sun, 7 Dec 2025 12:20:05 +0200 Subject: [PATCH 2/5] feat(response cache): add complete response snapshot caching with header filtering - Store full response (body, status, headers) instead of just body text - Filter sensitive headers (Set-Cookie, WWW-Authenticate, etc.) - Remove `respond` as it's inferred from `Content-Type` header - Add vitest tests --- packages/response-cache/package.json | 9 +- packages/response-cache/src/index.test.ts | 416 +++++++++++++++ packages/response-cache/src/index.ts | 62 ++- packages/response-cache/tsconfig.build.json | 5 + packages/response-cache/tsconfig.json | 18 +- packages/response-cache/tsconfig.spec.json | 8 + packages/response-cache/vitest.config.ts | 8 + yarn.lock | 544 ++++++++++++++++++-- 8 files changed, 988 insertions(+), 82 deletions(-) create mode 100644 packages/response-cache/src/index.test.ts create mode 100644 packages/response-cache/tsconfig.build.json create mode 100644 packages/response-cache/tsconfig.spec.json create mode 100644 packages/response-cache/vitest.config.ts diff --git a/packages/response-cache/package.json b/packages/response-cache/package.json index bed18024c..538a2c75b 100644 --- a/packages/response-cache/package.json +++ b/packages/response-cache/package.json @@ -1,7 +1,7 @@ { - "name": "@hono/redis-cache", + "name": "@hono/response-cache", "version": "0.0.0", - "description": "Redis cache for Hono", + "description": "Response cache for Hono", "type": "module", "main": "dist/index.cjs", "module": "dist/index.js", @@ -37,11 +37,12 @@ "hono": ">=4.7.0" }, "dependencies": { - "hono": ">=4.7.0" + "hono": "^4.10.1" }, "devDependencies": { "publint": "^0.2.7", "tsup": "^8.1.0", - "typescript": "^5.7.3" + "typescript": "^5.8.2", + "vitest": "^2.0.0" } } diff --git a/packages/response-cache/src/index.test.ts b/packages/response-cache/src/index.test.ts new file mode 100644 index 000000000..62660d076 --- /dev/null +++ b/packages/response-cache/src/index.test.ts @@ -0,0 +1,416 @@ +import { Hono } from 'hono' +import { vi } from 'vitest' +import { responseCache } from '.' + +const createMockStore = () => { + const storage = new Map() + return { + get: vi.fn((key: string) => storage.get(key) ?? null), + set: vi.fn((key: string, value: string) => { + storage.set(key, value) + }), + invalidate: vi.fn((key: string) => { + storage.delete(key) + }), + storage, + } +} + +describe('Response Cache Middleware', () => { + describe('Cache Miss', () => { + it('Should call handler on cache miss', async () => { + const store = createMockStore() + const app = new Hono() + const handlerSpy = vi.fn((c) => c.text('response')) + + app.use('*', responseCache({ store })) + app.get('/test', handlerSpy) + + await app.request(new Request('http://localhost/test')) + + expect(handlerSpy).toHaveBeenCalledTimes(1) + }) + + it('Should return complete response on cache miss', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.json({ data: 'test' }, 201, { 'X-Custom': 'header' })) + + const res = await app.request(new Request('http://localhost/test')) + + expect(res.status).toBe(201) + expect(res.headers.get('X-Custom')).toBe('header') + expect(res.headers.get('Content-Type')).toContain('application/json') + + const data = await res.json() + expect(data).toEqual({ data: 'test' }) + }) + }) + + describe('Cache Hit', () => { + it('Should return cached response on second call', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.json({ data: 'test' }, 200, { + 'X-Request-Id': '12345', + 'Cache-Control': 'max-age=3600', + })) + + const res1 = await app.request(new Request('http://localhost/test')) + expect(res1.status).toBe(200) + expect(res1.headers.get('X-Request-Id')).toBe('12345') + expect(await res1.json()).toEqual({ data: 'test' }) + + store.get.mockClear() + store.set.mockClear() + + const res2 = await app.request(new Request('http://localhost/test')) + + expect(res2.status).toBe(200) + expect(res2.headers.get('X-Request-Id')).toBe('12345') + expect(res2.headers.get('Cache-Control')).toBe('max-age=3600') + expect(await res2.json()).toEqual({ data: 'test' }) + expect(store.get).toHaveBeenCalledWith('/test') + expect(store.set).not.toHaveBeenCalled() + }) + + it('Should filter sensitive headers from cache', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => { + return c.json({ data: 'test' }, 200, { + 'Content-Type': 'application/json', + 'X-Custom': 'safe-header', + 'Set-Cookie': 'session=abc123', + 'WWW-Authenticate': 'Bearer token', + }) + }) + + await app.request(new Request('http://localhost/test')) + + const cachedValue = store.storage.get('/test') + const snapshot = JSON.parse(cachedValue!) + + expect(snapshot.headers['x-custom']).toBe('safe-header') + expect(snapshot.headers['content-type']).toContain('application/json') + expect(snapshot.headers['set-cookie']).toBeUndefined() + expect(snapshot.headers['www-authenticate']).toBeUndefined() + }) + }) + + describe('Response Types', () => { + it('Should cache JSON responses correctly', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.json({ message: 'hello', count: 42 })) + + const res1 = await app.request(new Request('http://localhost/test')) + const data1 = await res1.json() + expect(data1).toEqual({ message: 'hello', count: 42 }) + + const res2 = await app.request(new Request('http://localhost/test')) + const data2 = await res2.json() + expect(data2).toEqual({ message: 'hello', count: 42 }) + }) + + it('Should cache HTML responses correctly', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.html('
Test
')) + + const res1 = await app.request(new Request('http://localhost/test')) + expect(await res1.text()).toBe('
Test
') + + const res2 = await app.request(new Request('http://localhost/test')) + expect(await res2.text()).toBe('
Test
') + }) + + it('Should cache text responses correctly', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.text('Plain text response')) + + const res1 = await app.request(new Request('http://localhost/test')) + expect(await res1.text()).toBe('Plain text response') + + const res2 = await app.request(new Request('http://localhost/test')) + expect(await res2.text()).toBe('Plain text response') + }) + + it('Should cache body responses correctly', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.body('Body response')) + + const res1 = await app.request(new Request('http://localhost/test')) + expect(await res1.text()).toBe('Body response') + + const res2 = await app.request(new Request('http://localhost/test')) + expect(await res2.text()).toBe('Body response') + }) + }) + + describe('Custom Key Function', () => { + it('Should use request path as key by default', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + + expect(store.get).toHaveBeenCalledWith('/test') + expect(store.set).toHaveBeenCalledWith('/test', expect.any(String)) + }) + + it('Should use custom keyFn when provided', async () => { + const store = createMockStore() + const app = new Hono() + const customKeyFn = vi.fn((req, c) => `custom_${c.req.path}`) + + app.use('*', responseCache({ store, keyFn: customKeyFn })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + + expect(customKeyFn).toHaveBeenCalled() + expect(store.get).toHaveBeenCalledWith('custom_/test') + expect(store.set).toHaveBeenCalledWith('custom_/test', expect.any(String)) + }) + + it('Should pass correct arguments to keyFn', async () => { + const store = createMockStore() + const app = new Hono() + const customKeyFn = vi.fn((req, c) => c.req.path) + + app.use('*', responseCache({ store, keyFn: customKeyFn })) + app.get('/test', (c) => c.text('response')) + + const req = new Request('http://localhost/test') + await app.request(req) + + expect(customKeyFn).toHaveBeenCalledWith( + expect.objectContaining({ path: '/test' }), + expect.anything() + ) + }) + + it('Should maintain separate cache entries for different keys', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/page1', (c) => c.text('Page 1')) + app.get('/page2', (c) => c.text('Page 2')) + + const res1 = await app.request(new Request('http://localhost/page1')) + expect(await res1.text()).toBe('Page 1') + + const res2 = await app.request(new Request('http://localhost/page2')) + expect(await res2.text()).toBe('Page 2') + + expect(store.storage.has('/page1')).toBe(true) + expect(store.storage.has('/page2')).toBe(true) + expect(store.storage.get('/page1')).not.toBe(store.storage.get('/page2')) + }) + }) + + describe('Logging', () => { + it('Should call onHit callback on cache hit', async () => { + const store = createMockStore() + const onHit = vi.fn() + const app = new Hono() + + app.use('*', responseCache({ store, logging: { enabled: true, onHit } })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + expect(onHit).not.toHaveBeenCalled() + + await app.request(new Request('http://localhost/test')) + expect(onHit).toHaveBeenCalledWith('/test', expect.anything()) + }) + + it('Should call onMiss callback on cache miss', async () => { + const store = createMockStore() + const onMiss = vi.fn() + const app = new Hono() + + app.use('*', responseCache({ store, logging: { enabled: true, onMiss } })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + + expect(onMiss).toHaveBeenCalledWith('/test', expect.anything()) + }) + + it('Should not call logging callbacks when logging is disabled', async () => { + const store = createMockStore() + const onHit = vi.fn() + const onMiss = vi.fn() + const app = new Hono() + + app.use('*', responseCache({ store, logging: { enabled: false, onHit, onMiss } })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + expect(onMiss).not.toHaveBeenCalled() + + await app.request(new Request('http://localhost/test')) + expect(onHit).not.toHaveBeenCalled() + }) + + it('Should not call logging callbacks when logging is not provided', async () => { + const store = createMockStore() + const app = new Hono() + + app.use('*', responseCache({ store })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + await app.request(new Request('http://localhost/test')) + }) + + it('Should pass context to logging callbacks', async () => { + const store = createMockStore() + const onHit = vi.fn() + const app = new Hono() + + app.use('*', responseCache({ store, logging: { enabled: true, onHit } })) + app.get('/test', (c) => c.text('response')) + + await app.request(new Request('http://localhost/test')) + await app.request(new Request('http://localhost/test')) + + expect(onHit).toHaveBeenCalledWith( + '/test', + expect.objectContaining({ + req: expect.any(Object), + }) + ) + }) + }) + + describe('Error Handling', () => { + let consoleErrorSpy: ReturnType + + beforeEach(() => { + consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) + }) + + afterEach(() => { + consoleErrorSpy.mockRestore() + }) + + it('Should call onError callback when store.get throws', async () => { + const store = createMockStore() + const onError = vi.fn() + store.get.mockImplementation(() => { + throw new Error('Store get error') + }) + + const app = new Hono() + app.use('*', responseCache({ store, logging: { enabled: true, onError } })) + app.get('/test', (c) => c.text('response')) + + const res = await app.request(new Request('http://localhost/test')) + + expect(res.status).toBe(500) + expect(onError).toHaveBeenCalledWith( + '/test', + expect.anything(), + expect.objectContaining({ message: 'Store get error' }) + ) + }) + + it('Should call onError callback when store.set throws', async () => { + const store = createMockStore() + const onError = vi.fn() + store.set.mockImplementation(() => { + throw new Error('Store set error') + }) + + const app = new Hono() + app.use('*', responseCache({ store, logging: { enabled: true, onError } })) + app.get('/test', (c) => c.text('response')) + + const res = await app.request(new Request('http://localhost/test')) + + expect(res.status).toBe(500) + expect(onError).toHaveBeenCalledWith( + '/test', + expect.anything(), + expect.objectContaining({ message: 'Store set error' }) + ) + }) + }) + + describe('Integration Tests', () => { + it('Should work with async store operations', async () => { + const storage = new Map() + const asyncStore = { + get: vi.fn(async (key: string) => { + await new Promise((resolve) => setTimeout(resolve, 10)) + return storage.get(key) ?? null + }), + set: vi.fn(async (key: string, value: string) => { + await new Promise((resolve) => setTimeout(resolve, 10)) + storage.set(key, value) + }), + invalidate: vi.fn(async (key: string) => { + await new Promise((resolve) => setTimeout(resolve, 10)) + storage.delete(key) + }), + } + + const app = new Hono() + app.use('*', responseCache({ store: asyncStore })) + app.get('/test', (c) => c.text('async response')) + + const res1 = await app.request(new Request('http://localhost/test')) + expect(await res1.text()).toBe('async response') + + const res2 = await app.request(new Request('http://localhost/test')) + expect(await res2.text()).toBe('async response') + + expect(asyncStore.get).toHaveBeenCalledTimes(2) + expect(asyncStore.set).toHaveBeenCalledTimes(1) + }) + + it('Should work with query parameters in custom keyFn', async () => { + const store = createMockStore() + const app = new Hono() + const keyFn = (req: any, c: any) => `${c.req.path}?${c.req.query('id')}` + + app.use('*', responseCache({ store, keyFn })) + app.get('/item', (c) => { + const id = c.req.query('id') + return c.text(`Item ${id}`) + }) + + const res1 = await app.request(new Request('http://localhost/item?id=1')) + const res2 = await app.request(new Request('http://localhost/item?id=2')) + + expect(await res1.text()).toBe('Item 1') + expect(await res2.text()).toBe('Item 2') + + expect(store.storage.has('/item?1')).toBe(true) + expect(store.storage.has('/item?2')).toBe(true) + }) + }) +}) diff --git a/packages/response-cache/src/index.ts b/packages/response-cache/src/index.ts index 27c4e8e78..84ef90736 100644 --- a/packages/response-cache/src/index.ts +++ b/packages/response-cache/src/index.ts @@ -9,13 +9,24 @@ interface CacheStore { invalidate(key: string): TOrPromise } +const EXCLUDED_RESPONSE_HEADERS = new Set([ + 'set-cookie', 'www-authenticate', 'proxy-authenticate', 'authentication-info', + 'connection', 'keep-alive', 'upgrade', 'transfer-encoding', 'te', 'trailer', + 'via', 'age', 'warning', 'date', 'vary' +]) + +function filterHeaders(headers: Headers): Record { + const filtered: Record = {} + headers.forEach((value, key) => { + if (!EXCLUDED_RESPONSE_HEADERS.has(key.toLowerCase())) { + filtered[key] = value + } + }) + return filtered +} + interface CacheMiddlewareOptions { store: CacheStore; - /** - * The response type to cache. If not provided, the response will be returned as HTML. - * @default 'html' - */ - respond?: 'body' | 'text' | 'json' | 'html'; /** * Function to generate a cache key from the request and context. If not provided, the request path will be used. * @param req - The request object. @@ -30,31 +41,34 @@ interface CacheMiddlewareOptions { onError?: (key: string, c: Context, error: unknown) => void; }; } -const responseCache = ({ store, keyFn, respond = 'html', logging }: CacheMiddlewareOptions) => { +const responseCache = ({ store, keyFn, logging }: CacheMiddlewareOptions) => { return createMiddleware(async (c, next) => { const key = keyFn ? keyFn(c.req, c) : c.req.path - const cached = await store.get(key) try { + const cached = await store.get(key) if (cached) { if (logging?.enabled) {logging.onHit?.(key, c)} - if (respond === 'json') { - return c.json(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) - } - else if (respond === 'html') { - return await c.html(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) - } - else if (respond === 'text') { - return c.text(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) - } - else if (respond === 'body') { - return c.body(JSON.parse(cached), 200, Object.fromEntries(c.res.headers.entries())) + + const snapshot = JSON.parse(cached) + const { body, status, headers } = snapshot + + return new Response(body, { + status, + headers + }) + } else { + if (logging?.enabled) {logging.onMiss?.(key, c)} + await next() + + const body = await c.res.clone().text() + const status = c.res.status + const headers = filterHeaders(c.res.headers) + + const snapshot = JSON.stringify({ body, status, headers }) + await store.set(key, snapshot) + + return c.res } - } else { - if (logging?.enabled) {logging.onMiss?.(key, c)} - await next() - const response = await c.res.clone().text() - await store.set(key, JSON.stringify(response)) - } } catch (error) { if (logging?.enabled) {logging.onError?.(key, c, error)} throw error diff --git a/packages/response-cache/tsconfig.build.json b/packages/response-cache/tsconfig.build.json new file mode 100644 index 000000000..4a1f19acc --- /dev/null +++ b/packages/response-cache/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": {}, + "references": [] +} diff --git a/packages/response-cache/tsconfig.json b/packages/response-cache/tsconfig.json index 80831b3ee..d4ad6cfa3 100644 --- a/packages/response-cache/tsconfig.json +++ b/packages/response-cache/tsconfig.json @@ -1,10 +1,12 @@ { - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.build.json" }, - "include": [ - "src/**/*.ts" - ], - } \ No newline at end of file + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/response-cache/tsconfig.spec.json b/packages/response-cache/tsconfig.spec.json new file mode 100644 index 000000000..9e3aa44d6 --- /dev/null +++ b/packages/response-cache/tsconfig.spec.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "../../dist/packages/response-cache", + "types": ["vitest/globals"] + }, + "references": [] +} diff --git a/packages/response-cache/vitest.config.ts b/packages/response-cache/vitest.config.ts new file mode 100644 index 000000000..6f5da90b4 --- /dev/null +++ b/packages/response-cache/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineProject } from 'vitest/config' + +export default defineProject({ + test: { + globals: true, + include: ['src/**/*.test.ts'], + }, +}) diff --git a/yarn.lock b/yarn.lock index 9faa7d8c3..e653f7c0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1073,6 +1073,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/aix-ppc64@npm:0.21.5" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/aix-ppc64@npm:0.25.1" @@ -1101,6 +1108,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm64@npm:0.21.5" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm64@npm:0.25.1" @@ -1129,6 +1143,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-arm@npm:0.21.5" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-arm@npm:0.25.1" @@ -1157,6 +1178,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/android-x64@npm:0.21.5" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/android-x64@npm:0.25.1" @@ -1185,6 +1213,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-arm64@npm:0.21.5" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-arm64@npm:0.25.1" @@ -1213,6 +1248,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/darwin-x64@npm:0.21.5" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/darwin-x64@npm:0.25.1" @@ -1241,6 +1283,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-arm64@npm:0.21.5" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-arm64@npm:0.25.1" @@ -1269,6 +1318,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/freebsd-x64@npm:0.21.5" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/freebsd-x64@npm:0.25.1" @@ -1297,6 +1353,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm64@npm:0.21.5" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm64@npm:0.25.1" @@ -1325,6 +1388,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-arm@npm:0.21.5" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-arm@npm:0.25.1" @@ -1353,6 +1423,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ia32@npm:0.21.5" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ia32@npm:0.25.1" @@ -1381,6 +1458,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-loong64@npm:0.21.5" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-loong64@npm:0.25.1" @@ -1409,6 +1493,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-mips64el@npm:0.21.5" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-mips64el@npm:0.25.1" @@ -1437,6 +1528,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-ppc64@npm:0.21.5" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-ppc64@npm:0.25.1" @@ -1465,6 +1563,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-riscv64@npm:0.21.5" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-riscv64@npm:0.25.1" @@ -1493,6 +1598,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-s390x@npm:0.21.5" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-s390x@npm:0.25.1" @@ -1521,6 +1633,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/linux-x64@npm:0.21.5" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/linux-x64@npm:0.25.1" @@ -1577,6 +1696,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/netbsd-x64@npm:0.21.5" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/netbsd-x64@npm:0.25.1" @@ -1633,6 +1759,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/openbsd-x64@npm:0.21.5" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/openbsd-x64@npm:0.25.1" @@ -1675,6 +1808,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/sunos-x64@npm:0.21.5" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/sunos-x64@npm:0.25.1" @@ -1703,6 +1843,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-arm64@npm:0.21.5" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-arm64@npm:0.25.1" @@ -1731,6 +1878,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-ia32@npm:0.21.5" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-ia32@npm:0.25.1" @@ -1759,6 +1913,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.21.5": + version: 0.21.5 + resolution: "@esbuild/win32-x64@npm:0.21.5" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.25.1": version: 0.25.1 resolution: "@esbuild/win32-x64@npm:0.25.1" @@ -2438,14 +2599,15 @@ __metadata: languageName: unknown linkType: soft -"@hono/redis-cache@workspace:packages/response-cache": +"@hono/response-cache@workspace:packages/response-cache": version: 0.0.0-use.local - resolution: "@hono/redis-cache@workspace:packages/response-cache" + resolution: "@hono/response-cache@workspace:packages/response-cache" dependencies: - hono: "npm:>=4.7.0" + hono: "npm:^4.10.1" publint: "npm:^0.2.7" tsup: "npm:^8.1.0" - typescript: "npm:^5.7.3" + typescript: "npm:^5.8.2" + vitest: "npm:^2.0.0" peerDependencies: hono: ">=4.7.0" languageName: unknown @@ -5637,6 +5799,18 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/expect@npm:2.1.9" + dependencies: + "@vitest/spy": "npm:2.1.9" + "@vitest/utils": "npm:2.1.9" + chai: "npm:^5.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/98d1cf02917316bebef9e4720723e38298a1c12b3c8f3a81f259bb822de4288edf594e69ff64f0b88afbda6d04d7a4f0c2f720f3fec16b4c45f5e2669f09fdbb + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -5650,6 +5824,25 @@ __metadata: languageName: node linkType: hard +"@vitest/mocker@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/mocker@npm:2.1.9" + dependencies: + "@vitest/spy": "npm:2.1.9" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.12" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/f734490d8d1206a7f44dfdfca459282f5921d73efa72935bb1dc45307578defd38a4131b14853316373ec364cbe910dbc74594ed4137e0da35aa4d9bb716f190 + languageName: node + linkType: hard + "@vitest/mocker@npm:3.2.4": version: 3.2.4 resolution: "@vitest/mocker@npm:3.2.4" @@ -5669,6 +5862,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:2.1.9, @vitest/pretty-format@npm:^2.1.9": + version: 2.1.9 + resolution: "@vitest/pretty-format@npm:2.1.9" + dependencies: + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/155f9ede5090eabed2a73361094bb35ed4ec6769ae3546d2a2af139166569aec41bb80e031c25ff2da22b71dd4ed51e5468e66a05e6aeda5f14b32e30bc18f00 + languageName: node + linkType: hard + "@vitest/pretty-format@npm:3.2.4, @vitest/pretty-format@npm:^3.2.4": version: 3.2.4 resolution: "@vitest/pretty-format@npm:3.2.4" @@ -5678,6 +5880,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/runner@npm:2.1.9" + dependencies: + "@vitest/utils": "npm:2.1.9" + pathe: "npm:^1.1.2" + checksum: 10c0/e81f176badb12a815cbbd9bd97e19f7437a0b64e8934d680024b0f768d8670d59cad698ef0e3dada5241b6731d77a7bb3cd2c7cb29f751fd4dd35eb11c42963a + languageName: node + linkType: hard + "@vitest/runner@npm:3.2.4": version: 3.2.4 resolution: "@vitest/runner@npm:3.2.4" @@ -5689,6 +5901,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/snapshot@npm:2.1.9" + dependencies: + "@vitest/pretty-format": "npm:2.1.9" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + checksum: 10c0/394974b3a1fe96186a3c87f933b2f7f1f7b7cc42f9c781d80271dbb4c987809bf035fecd7398b8a3a2d54169e3ecb49655e38a0131d0e7fea5ce88960613b526 + languageName: node + linkType: hard + "@vitest/snapshot@npm:3.2.4": version: 3.2.4 resolution: "@vitest/snapshot@npm:3.2.4" @@ -5700,6 +5923,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/spy@npm:2.1.9" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 10c0/12a59b5095e20188b819a1d797e0a513d991b4e6a57db679927c43b362a3eff52d823b34e855a6dd9e73c9fa138dcc5ef52210841a93db5cbf047957a60ca83c + languageName: node + linkType: hard + "@vitest/spy@npm:3.2.4": version: 3.2.4 resolution: "@vitest/spy@npm:3.2.4" @@ -5709,6 +5941,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:2.1.9": + version: 2.1.9 + resolution: "@vitest/utils@npm:2.1.9" + dependencies: + "@vitest/pretty-format": "npm:2.1.9" + loupe: "npm:^3.1.2" + tinyrainbow: "npm:^1.2.0" + checksum: 10c0/81a346cd72b47941f55411f5df4cc230e5f740d1e97e0d3f771b27f007266fc1f28d0438582f6409ea571bc0030ed37f684c64c58d1947d6298d770c21026fdf + languageName: node + linkType: hard + "@vitest/utils@npm:3.2.4": version: 3.2.4 resolution: "@vitest/utils@npm:3.2.4" @@ -6636,6 +6879,19 @@ __metadata: languageName: node linkType: hard +"chai@npm:^5.1.2": + version: 5.3.3 + resolution: "chai@npm:5.3.3" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/b360fd4d38861622e5010c2f709736988b05c7f31042305fa3f4e9911f6adb80ccfb4e302068bf8ed10e835c2e2520cba0f5edc13d878b886987e5aa62483f53 + languageName: node + linkType: hard + "chai@npm:^5.2.0": version: 5.2.0 resolution: "chai@npm:5.2.0" @@ -7492,7 +7748,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.4.3": +"debug@npm:^4.3.7, debug@npm:^4.4.3": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -8010,7 +8266,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.7.0": +"es-module-lexer@npm:^1.5.4, es-module-lexer@npm:^1.7.0": version: 1.7.0 resolution: "es-module-lexer@npm:1.7.0" checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b @@ -8157,6 +8413,86 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.21.3": + version: 0.21.5 + resolution: "esbuild@npm:0.21.5" + dependencies: + "@esbuild/aix-ppc64": "npm:0.21.5" + "@esbuild/android-arm": "npm:0.21.5" + "@esbuild/android-arm64": "npm:0.21.5" + "@esbuild/android-x64": "npm:0.21.5" + "@esbuild/darwin-arm64": "npm:0.21.5" + "@esbuild/darwin-x64": "npm:0.21.5" + "@esbuild/freebsd-arm64": "npm:0.21.5" + "@esbuild/freebsd-x64": "npm:0.21.5" + "@esbuild/linux-arm": "npm:0.21.5" + "@esbuild/linux-arm64": "npm:0.21.5" + "@esbuild/linux-ia32": "npm:0.21.5" + "@esbuild/linux-loong64": "npm:0.21.5" + "@esbuild/linux-mips64el": "npm:0.21.5" + "@esbuild/linux-ppc64": "npm:0.21.5" + "@esbuild/linux-riscv64": "npm:0.21.5" + "@esbuild/linux-s390x": "npm:0.21.5" + "@esbuild/linux-x64": "npm:0.21.5" + "@esbuild/netbsd-x64": "npm:0.21.5" + "@esbuild/openbsd-x64": "npm:0.21.5" + "@esbuild/sunos-x64": "npm:0.21.5" + "@esbuild/win32-arm64": "npm:0.21.5" + "@esbuild/win32-ia32": "npm:0.21.5" + "@esbuild/win32-x64": "npm:0.21.5" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fa08508adf683c3f399e8a014a6382a6b65542213431e26206c0720e536b31c09b50798747c2a105a4bbba1d9767b8d3615a74c2f7bf1ddf6d836cd11eb672de + languageName: node + linkType: hard + "esbuild@npm:^0.25.0": version: 0.25.1 resolution: "esbuild@npm:0.25.1" @@ -8921,6 +9257,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.1.0": + version: 1.2.2 + resolution: "expect-type@npm:1.2.2" + checksum: 10c0/6019019566063bbc7a690d9281d920b1a91284a4a093c2d55d71ffade5ac890cf37a51e1da4602546c4b56569d2ad2fc175a2ccee77d1ae06cb3af91ef84f44b + languageName: node + linkType: hard + "expect-type@npm:^1.2.1": version: 1.2.1 resolution: "expect-type@npm:1.2.1" @@ -10138,13 +10481,6 @@ __metadata: languageName: node linkType: hard -"hono@npm:>=4.7.0": - version: 4.10.7 - resolution: "hono@npm:4.10.7" - checksum: 10c0/11aec63ce11b0d635b7ecf24009ba2b694eac7a8ef8e3b7c1b2c7898dc6b39b7a6de1a8454608aade8c66685b016c0e5483b253d55431210a9116461f1a670ea - languageName: node - linkType: hard - "hono@npm:^4.10.1": version: 4.10.3 resolution: "hono@npm:4.10.3" @@ -11384,6 +11720,13 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^3.1.2": + version: 3.2.1 + resolution: "loupe@npm:3.2.1" + checksum: 10c0/910c872cba291309664c2d094368d31a68907b6f5913e989d301b5c25f30e97d76d77f23ab3bf3b46d0f601ff0b6af8810c10c31b91d2c6b2f132809ca2cc705 + languageName: node + linkType: hard + "loupe@npm:^3.1.4": version: 3.1.4 resolution: "loupe@npm:3.1.4" @@ -11456,6 +11799,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.12, magic-string@npm:^0.30.19": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + "magic-string@npm:^0.30.14": version: 0.30.19 resolution: "magic-string@npm:0.30.19" @@ -11474,15 +11826,6 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.19": - version: 0.30.21 - resolution: "magic-string@npm:0.30.21" - dependencies: - "@jridgewell/sourcemap-codec": "npm:^1.5.5" - checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a - languageName: node - linkType: hard - "magicast@npm:^0.3.5": version: 0.3.5 resolution: "magicast@npm:0.3.5" @@ -13591,7 +13934,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.5.3, postcss@npm:^8.5.6": +"postcss@npm:^8.4.43, postcss@npm:^8.5.3, postcss@npm:^8.5.6": version: 8.5.6 resolution: "postcss@npm:8.5.6" dependencies: @@ -14542,7 +14885,7 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.34.8": +"rollup@npm:^4.20.0, rollup@npm:^4.34.8": version: 4.53.3 resolution: "rollup@npm:4.53.3" dependencies: @@ -15616,6 +15959,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.8.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f + languageName: node + linkType: hard + "std-env@npm:^3.9.0": version: 3.9.0 resolution: "std-env@npm:3.9.0" @@ -16095,7 +16445,7 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^0.3.2": +"tinyexec@npm:^0.3.1, tinyexec@npm:^0.3.2": version: 0.3.2 resolution: "tinyexec@npm:0.3.2" checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 @@ -16139,13 +16489,20 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.1.1": +"tinypool@npm:^1.0.1, tinypool@npm:^1.1.1": version: 1.1.1 resolution: "tinypool@npm:1.1.1" checksum: 10c0/bf26727d01443061b04fa863f571016950888ea994ba0cd8cba3a1c51e2458d84574341ab8dbc3664f1c3ab20885c8cf9ff1cc4b18201f04c2cde7d317fff69b languageName: node linkType: hard +"tinyrainbow@npm:^1.2.0": + version: 1.2.0 + resolution: "tinyrainbow@npm:1.2.0" + checksum: 10c0/7f78a4b997e5ba0f5ecb75e7ed786f30bab9063716e7dff24dd84013fb338802e43d176cb21ed12480561f5649a82184cf31efb296601a29d38145b1cdb4c192 + languageName: node + linkType: hard + "tinyrainbow@npm:^2.0.0": version: 2.0.0 resolution: "tinyrainbow@npm:2.0.0" @@ -16153,6 +16510,13 @@ __metadata: languageName: node linkType: hard +"tinyspy@npm:^3.0.2": + version: 3.0.2 + resolution: "tinyspy@npm:3.0.2" + checksum: 10c0/55ffad24e346622b59292e097c2ee30a63919d5acb7ceca87fc0d1c223090089890587b426e20054733f97a58f20af2c349fb7cc193697203868ab7ba00bcea0 + languageName: node + linkType: hard + "tinyspy@npm:^4.0.3": version: 4.0.3 resolution: "tinyspy@npm:4.0.3" @@ -16632,16 +16996,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.7.3": - version: 5.9.3 - resolution: "typescript@npm:5.9.3" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/6bd7552ce39f97e711db5aa048f6f9995b53f1c52f7d8667c1abdc1700c68a76a308f579cd309ce6b53646deb4e9a1be7c813a93baaf0a28ccd536a30270e1c5 - languageName: node - linkType: hard - "typescript@npm:^5.8.2": version: 5.8.2 resolution: "typescript@npm:5.8.2" @@ -16672,16 +17026,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.7.3#optional!builtin": - version: 5.9.3 - resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 10c0/ad09fdf7a756814dce65bc60c1657b40d44451346858eea230e10f2e95a289d9183b6e32e5c11e95acc0ccc214b4f36289dcad4bf1886b0adb84d711d336a430 - languageName: node - linkType: hard - "typescript@patch:typescript@npm%3A^5.8.2#optional!builtin": version: 5.8.2 resolution: "typescript@patch:typescript@npm%3A5.8.2#optional!builtin::version=5.8.2&hash=5786d5" @@ -17303,6 +17647,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:2.1.9": + version: 2.1.9 + resolution: "vite-node@npm:2.1.9" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.7" + es-module-lexer: "npm:^1.5.4" + pathe: "npm:^1.1.2" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/0d3589f9f4e9cff696b5b49681fdb75d1638c75053728be52b4013f70792f38cb0120a9c15e3a4b22bdd6b795ad7c2da13bcaf47242d439f0906049e73bdd756 + languageName: node + linkType: hard + "vite-node@npm:3.2.4": version: 3.2.4 resolution: "vite-node@npm:3.2.4" @@ -17373,6 +17732,49 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.0.0": + version: 5.4.21 + resolution: "vite@npm:5.4.21" + dependencies: + esbuild: "npm:^0.21.3" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.43" + rollup: "npm:^4.20.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/468336a1409f728b464160cbf02672e72271fb688d0e605e776b74a89d27e1029509eef3a3a6c755928d8011e474dbf234824d054d07960be5f23cd176bc72de + languageName: node + linkType: hard + "vite@npm:^5.0.0 || ^6.0.0 || ^7.0.0-0": version: 7.0.0-beta.1 resolution: "vite@npm:7.0.0-beta.1" @@ -17483,6 +17885,56 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^2.0.0": + version: 2.1.9 + resolution: "vitest@npm:2.1.9" + dependencies: + "@vitest/expect": "npm:2.1.9" + "@vitest/mocker": "npm:2.1.9" + "@vitest/pretty-format": "npm:^2.1.9" + "@vitest/runner": "npm:2.1.9" + "@vitest/snapshot": "npm:2.1.9" + "@vitest/spy": "npm:2.1.9" + "@vitest/utils": "npm:2.1.9" + chai: "npm:^5.1.2" + debug: "npm:^4.3.7" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.12" + pathe: "npm:^1.1.2" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.1" + tinypool: "npm:^1.0.1" + tinyrainbow: "npm:^1.2.0" + vite: "npm:^5.0.0" + vite-node: "npm:2.1.9" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 2.1.9 + "@vitest/ui": 2.1.9 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/e339e16dccacf4589ff43cb1f38c7b4d14427956ae8ef48702af6820a9842347c2b6c77356aeddb040329759ca508a3cb2b104ddf78103ea5bc98ab8f2c3a54e + languageName: node + linkType: hard + "vitest@npm:^3.2.4": version: 3.2.4 resolution: "vitest@npm:3.2.4" From cc46026c807885ae226a583f1cbb30eda5b17a73 Mon Sep 17 00:00:00 2001 From: Rokas Muningis <28229273+muningis@users.noreply.github.com> Date: Sun, 7 Dec 2025 12:23:08 +0200 Subject: [PATCH 3/5] docs(response cache): add changeset --- .changeset/khaki-tires-roll.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/khaki-tires-roll.md diff --git a/.changeset/khaki-tires-roll.md b/.changeset/khaki-tires-roll.md new file mode 100644 index 000000000..01234016d --- /dev/null +++ b/.changeset/khaki-tires-roll.md @@ -0,0 +1,5 @@ +--- +'@hono/response-cache': major +--- + +Initial Response Cache middleware implementation From 785c5fba9d92226c75b6aa494ab5e96b1a6a1fc5 Mon Sep 17 00:00:00 2001 From: Rokas Muningis <28229273+muningis@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:07:04 +0200 Subject: [PATCH 4/5] feat(response cache): adjust according to PR comments --- packages/response-cache/src/index.test.ts | 21 ++------------------- packages/response-cache/src/index.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 25 deletions(-) diff --git a/packages/response-cache/src/index.test.ts b/packages/response-cache/src/index.test.ts index 62660d076..04a3d2dba 100644 --- a/packages/response-cache/src/index.test.ts +++ b/packages/response-cache/src/index.test.ts @@ -181,7 +181,7 @@ describe('Response Cache Middleware', () => { it('Should use custom keyFn when provided', async () => { const store = createMockStore() const app = new Hono() - const customKeyFn = vi.fn((req, c) => `custom_${c.req.path}`) + const customKeyFn = vi.fn((c) => `custom_${c.req.path}`) app.use('*', responseCache({ store, keyFn: customKeyFn })) app.get('/test', (c) => c.text('response')) @@ -193,23 +193,6 @@ describe('Response Cache Middleware', () => { expect(store.set).toHaveBeenCalledWith('custom_/test', expect.any(String)) }) - it('Should pass correct arguments to keyFn', async () => { - const store = createMockStore() - const app = new Hono() - const customKeyFn = vi.fn((req, c) => c.req.path) - - app.use('*', responseCache({ store, keyFn: customKeyFn })) - app.get('/test', (c) => c.text('response')) - - const req = new Request('http://localhost/test') - await app.request(req) - - expect(customKeyFn).toHaveBeenCalledWith( - expect.objectContaining({ path: '/test' }), - expect.anything() - ) - }) - it('Should maintain separate cache entries for different keys', async () => { const store = createMockStore() const app = new Hono() @@ -395,7 +378,7 @@ describe('Response Cache Middleware', () => { it('Should work with query parameters in custom keyFn', async () => { const store = createMockStore() const app = new Hono() - const keyFn = (req: any, c: any) => `${c.req.path}?${c.req.query('id')}` + const keyFn = (c: any) => `${c.req.path}?${c.req.query('id')}` app.use('*', responseCache({ store, keyFn })) app.get('/item', (c) => { diff --git a/packages/response-cache/src/index.ts b/packages/response-cache/src/index.ts index 84ef90736..a06e63c35 100644 --- a/packages/response-cache/src/index.ts +++ b/packages/response-cache/src/index.ts @@ -1,5 +1,6 @@ -import type { Context, HonoRequest } from 'hono' +import type { Context, MiddlewareHandler } from 'hono' import { createMiddleware } from 'hono/factory' +import { encodeBase64, decodeBase64 } from 'hono/utils/encode' type TOrPromise = T | Promise @@ -33,7 +34,7 @@ interface CacheMiddlewareOptions { * @param c - The context object. * @returns A string key for the cache. */ - keyFn?: (req: HonoRequest, c: Context) => string; + keyFn?: (c: Context) => string; logging?: { enabled?: boolean; onHit?: (key: string, c: Context) => void; @@ -41,9 +42,9 @@ interface CacheMiddlewareOptions { onError?: (key: string, c: Context, error: unknown) => void; }; } -const responseCache = ({ store, keyFn, logging }: CacheMiddlewareOptions) => { +const responseCache = ({ store, keyFn, logging }: CacheMiddlewareOptions): MiddlewareHandler => { return createMiddleware(async (c, next) => { - const key = keyFn ? keyFn(c.req, c) : c.req.path + const key = keyFn ? keyFn(c) : c.req.path try { const cached = await store.get(key) if (cached) { @@ -52,7 +53,7 @@ const responseCache = ({ store, keyFn, logging }: CacheMiddlewareOptions) => { const snapshot = JSON.parse(cached) const { body, status, headers } = snapshot - return new Response(body, { + return new Response(decodeBase64(body), { status, headers }) @@ -60,7 +61,8 @@ const responseCache = ({ store, keyFn, logging }: CacheMiddlewareOptions) => { if (logging?.enabled) {logging.onMiss?.(key, c)} await next() - const body = await c.res.clone().text() + const bodyBuffer = await c.res.clone().arrayBuffer() + const body = encodeBase64(bodyBuffer) const status = c.res.status const headers = filterHeaders(c.res.headers) From e1ae8213919080c73255e7e35e285013831bdb6b Mon Sep 17 00:00:00 2001 From: Rokas Muningis <28229273+muningis@users.noreply.github.com> Date: Wed, 17 Dec 2025 00:12:43 +0200 Subject: [PATCH 5/5] chore: make it minor release instead of major --- .changeset/khaki-tires-roll.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/khaki-tires-roll.md b/.changeset/khaki-tires-roll.md index 01234016d..e5531b6ad 100644 --- a/.changeset/khaki-tires-roll.md +++ b/.changeset/khaki-tires-roll.md @@ -1,5 +1,5 @@ --- -'@hono/response-cache': major +'@hono/response-cache': minor --- Initial Response Cache middleware implementation