diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8eeb714ca..e1523a10c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,9 +77,9 @@ jobs: - uses: denoland/setup-deno@v1 with: deno-version: v1.x - - run: env NAME=Deno deno test --coverage=coverage/raw/deno-runtime --allow-read --allow-env --allow-write --allow-net -c runtime-tests/deno/deno.json runtime-tests/deno - - run: deno test -c runtime-tests/deno-jsx/deno.precompile.json --coverage=coverage/raw/deno-precompile-jsx runtime-tests/deno-jsx - - run: deno test -c runtime-tests/deno-jsx/deno.react-jsx.json --coverage=coverage/raw/deno-react-jsx runtime-tests/deno-jsx + - run: env NAME=Deno deno test --coverage=coverage/raw/deno-runtime --allow-read --allow-env --allow-write --allow-net -c runtime_tests/deno/deno.json runtime_tests/deno + - run: deno test -c runtime_tests/deno-jsx/deno.precompile.json --coverage=coverage/raw/deno-precompile-jsx runtime_tests/deno-jsx + - run: deno test -c runtime_tests/deno-jsx/deno.react-jsx.json --coverage=coverage/raw/deno-react-jsx runtime_tests/deno-jsx - uses: actions/upload-artifact@v4 with: name: coverage-deno diff --git a/package.json b/package.json index cc7bf9425..9ff375ac4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hono", - "version": "4.6.2", + "version": "4.6.1", "description": "Web framework built on Web Standards", "main": "dist/cjs/index.js", "type": "module", @@ -12,18 +12,18 @@ "scripts": { "test": "tsc --noEmit && vitest --run && vitest -c .vitest.config/jsx-runtime-default.ts --run && vitest -c .vitest.config/jsx-runtime-dom.ts --run", "test:watch": "vitest --watch", - "test:deno": "deno test --allow-read --allow-env --allow-write --allow-net -c runtime-tests/deno/deno.json runtime-tests/deno && deno test --no-lock -c runtime-tests/deno-jsx/deno.precompile.json runtime-tests/deno-jsx && deno test --no-lock -c runtime-tests/deno-jsx/deno.react-jsx.json runtime-tests/deno-jsx", - "test:bun": "bun test --jsx-import-source ../../src/jsx runtime-tests/bun/index.test.tsx", - "test:fastly": "vitest --run --config ./runtime-tests/fastly/vitest.config.ts", - "test:node": "vitest --run --config ./runtime-tests/node/vitest.config.ts", - "test:workerd": "vitest --run --config ./runtime-tests/workerd/vitest.config.ts", - "test:lambda": "vitest --run --config ./runtime-tests/lambda/vitest.config.ts", - "test:lambda-edge": "vitest --run --config ./runtime-tests/lambda-edge/vitest.config.ts", + "test:deno": "deno test --allow-read --allow-env --allow-write --allow-net -c runtime_tests/deno/deno.json runtime_tests/deno && deno test --no-lock -c runtime_tests/deno-jsx/deno.precompile.json runtime_tests/deno-jsx && deno test --no-lock -c runtime_tests/deno-jsx/deno.react-jsx.json runtime_tests/deno-jsx", + "test:bun": "bun test --jsx-import-source ../../src/jsx runtime_tests/bun/index.test.tsx", + "test:fastly": "vitest --run --config ./runtime_tests/fastly/vitest.config.ts", + "test:node": "vitest --run --config ./runtime_tests/node/vitest.config.ts", + "test:workerd": "vitest --run --config ./runtime_tests/workerd/vitest.config.ts", + "test:lambda": "vitest --run --config ./runtime_tests/lambda/vitest.config.ts", + "test:lambda-edge": "vitest --run --config ./runtime_tests/lambda-edge/vitest.config.ts", "test:all": "bun run test && bun test:deno && bun test:bun && bun test:fastly && bun test:node && bun test:workerd && bun test:lambda && bun test:lambda-edge", - "lint": "eslint src runtime-tests", - "lint:fix": "eslint src runtime-tests --fix", - "format": "prettier --check --cache \"src/**/*.{js,ts,tsx}\" \"runtime-tests/**/*.{js,ts,tsx}\"", - "format:fix": "prettier --write --cache --cache-strategy metadata \"src/**/*.{js,ts,tsx}\" \"runtime-tests/**/*.{js,ts,tsx}\"", + "lint": "eslint src runtime_tests", + "lint:fix": "eslint src runtime_tests --fix", + "format": "prettier --check --cache \"src/**/*.{js,ts,tsx}\" \"runtime_tests/**/*.{js,ts,tsx}\"", + "format:fix": "prettier --write --cache --cache-strategy metadata \"src/**/*.{js,ts,tsx}\" \"runtime_tests/**/*.{js,ts,tsx}\"", "copy:package.cjs.json": "cp ./package.cjs.json ./dist/cjs/package.json && cp ./package.cjs.json ./dist/types/package.json ", "build": "bun run --shell bun remove-dist && bun ./build.ts && bun run copy:package.cjs.json", "postbuild": "publint", diff --git a/runtime-tests/deno/deno.lock b/runtime-tests/deno/deno.lock deleted file mode 100644 index 49d0b042e..000000000 --- a/runtime-tests/deno/deno.lock +++ /dev/null @@ -1,34 +0,0 @@ -{ - "version": "3", - "packages": { - "specifiers": { - "jsr:@std/assert@^1.0.3": "jsr:@std/assert@1.0.3", - "jsr:@std/internal@^1.0.2": "jsr:@std/internal@1.0.2", - "jsr:@std/testing@^1.0.1": "jsr:@std/testing@1.0.1" - }, - "jsr": { - "@std/assert@1.0.3": { - "integrity": "b0d03ce1ced880df67132eea140623010d415848df66f6aa5df76507ca7c26d8", - "dependencies": [ - "jsr:@std/internal@^1.0.2" - ] - }, - "@std/internal@1.0.2": { - "integrity": "f4cabe2021352e8bfc24e6569700df87bf070914fc38d4b23eddd20108ac4495" - }, - "@std/testing@1.0.1": { - "integrity": "9c25841137ee818933e1722091bb9ed5fdc251c35e84c97979a52196bdb6c5c3", - "dependencies": [ - "jsr:@std/assert@^1.0.3" - ] - } - } - }, - "remote": {}, - "workspace": { - "dependencies": [ - "jsr:@std/assert@^1.0.3", - "jsr:@std/testing@^1.0.1" - ] - } -} diff --git a/runtime-tests/bun/.static/plain.txt b/runtime_tests/bun/.static/plain.txt similarity index 100% rename from runtime-tests/bun/.static/plain.txt rename to runtime_tests/bun/.static/plain.txt diff --git a/runtime-tests/bun/favicon.ico b/runtime_tests/bun/favicon.ico similarity index 100% rename from runtime-tests/bun/favicon.ico rename to runtime_tests/bun/favicon.ico diff --git a/runtime-tests/bun/index.test.tsx b/runtime_tests/bun/index.test.tsx similarity index 98% rename from runtime-tests/bun/index.test.tsx rename to runtime_tests/bun/index.test.tsx index 8aaed909b..a9d4bddbc 100644 --- a/runtime-tests/bun/index.test.tsx +++ b/runtime_tests/bun/index.test.tsx @@ -83,10 +83,10 @@ describe('Basic Auth Middleware', () => { describe('Serve Static Middleware', () => { const app = new Hono() const onNotFound = vi.fn(() => {}) - app.all('/favicon.ico', serveStatic({ path: './runtime-tests/bun/favicon.ico' })) + app.all('/favicon.ico', serveStatic({ path: './runtime_tests/bun/favicon.ico' })) app.all( '/favicon-notfound.ico', - serveStatic({ path: './runtime-tests/bun/favicon-notfound.ico', onNotFound }) + serveStatic({ path: './runtime_tests/bun/favicon-notfound.ico', onNotFound }) ) app.use('/favicon-notfound.ico', async (c, next) => { await next() @@ -95,14 +95,14 @@ describe('Serve Static Middleware', () => { app.get( '/static/*', serveStatic({ - root: './runtime-tests/bun/', + root: './runtime_tests/bun/', onNotFound, }) ) app.get( '/dot-static/*', serveStatic({ - root: './runtime-tests/bun/', + root: './runtime_tests/bun/', rewriteRequestPath: (path) => path.replace(/^\/dot-static/, './.static'), }) ) @@ -121,7 +121,7 @@ describe('Serve Static Middleware', () => { expect(res.status).toBe(404) expect(res.headers.get('X-Custom')).toBe('Bun') expect(onNotFound).toHaveBeenCalledWith( - './runtime-tests/bun/favicon-notfound.ico', + './runtime_tests/bun/favicon-notfound.ico', expect.anything() ) }) diff --git a/runtime-tests/bun/static/download b/runtime_tests/bun/static/download similarity index 100% rename from runtime-tests/bun/static/download rename to runtime_tests/bun/static/download diff --git a/runtime-tests/bun/static/hello.world/index.html b/runtime_tests/bun/static/hello.world/index.html similarity index 100% rename from runtime-tests/bun/static/hello.world/index.html rename to runtime_tests/bun/static/hello.world/index.html diff --git a/runtime-tests/bun/static/helloworld/index.html b/runtime_tests/bun/static/helloworld/index.html similarity index 100% rename from runtime-tests/bun/static/helloworld/index.html rename to runtime_tests/bun/static/helloworld/index.html diff --git a/runtime-tests/bun/static/plain.txt b/runtime_tests/bun/static/plain.txt similarity index 100% rename from runtime-tests/bun/static/plain.txt rename to runtime_tests/bun/static/plain.txt diff --git a/runtime-tests/bun/vitest.config.ts b/runtime_tests/bun/vitest.config.ts similarity index 83% rename from runtime-tests/bun/vitest.config.ts rename to runtime_tests/bun/vitest.config.ts index df1122b6c..6b1cae417 100644 --- a/runtime-tests/bun/vitest.config.ts +++ b/runtime_tests/bun/vitest.config.ts @@ -5,7 +5,7 @@ import config from '../../vitest.config' export default defineConfig({ test: { globals: true, - include: ['**/runtime-tests/bun/**/*.+(ts|tsx|js)'], + include: ['**/runtime_tests/bun/**/*.+(ts|tsx|js)'], coverage: { ...config.test?.coverage, reportsDirectory: './coverage/raw/runtime-bun', diff --git a/runtime-tests/deno-jsx/deno.precompile.json b/runtime_tests/deno-jsx/deno.precompile.json similarity index 100% rename from runtime-tests/deno-jsx/deno.precompile.json rename to runtime_tests/deno-jsx/deno.precompile.json diff --git a/runtime-tests/deno-jsx/deno.react-jsx.json b/runtime_tests/deno-jsx/deno.react-jsx.json similarity index 100% rename from runtime-tests/deno-jsx/deno.react-jsx.json rename to runtime_tests/deno-jsx/deno.react-jsx.json diff --git a/runtime-tests/deno-jsx/jsx.test.tsx b/runtime_tests/deno-jsx/jsx.test.tsx similarity index 100% rename from runtime-tests/deno-jsx/jsx.test.tsx rename to runtime_tests/deno-jsx/jsx.test.tsx diff --git a/runtime-tests/deno/.static/plain.txt b/runtime_tests/deno/.static/plain.txt similarity index 100% rename from runtime-tests/deno/.static/plain.txt rename to runtime_tests/deno/.static/plain.txt diff --git a/runtime-tests/deno/.vscode/settings.json b/runtime_tests/deno/.vscode/settings.json similarity index 100% rename from runtime-tests/deno/.vscode/settings.json rename to runtime_tests/deno/.vscode/settings.json diff --git a/runtime_tests/deno/cache.test.ts b/runtime_tests/deno/cache.test.ts new file mode 100644 index 000000000..583a90cd8 --- /dev/null +++ b/runtime_tests/deno/cache.test.ts @@ -0,0 +1,132 @@ +import { expect } from '@std/expect' +import { FakeTime } from '@std/testing/time' +import { Hono } from '../../src/hono.ts' +import { cache } from '../../src/middleware/cache/index.ts' + +Deno.test('Should return cached response', async () => { + const c = await caches.open('my-app') + await c.delete('http://localhost') + + let oneTimeWord = 'Hello Hono' + + const app = new Hono() + app.use( + '/', + cache({ + cacheName: 'my-app', + wait: true, + }) + ) + app.get('/', (c) => { + const result = oneTimeWord + oneTimeWord = 'Not Found' + return c.text(result) + }) + + await app.request('http://localhost') + const res = await app.request('http://localhost') + expect(await res.text()).toBe('Hello Hono') + expect(res.headers.get('Hono-Cache-Expires')).toBeNull() + expect(res).not.toBeNull() + expect(res.status).toBe(200) + await c.delete('http://localhost') +}) + +Deno.test( + { + name: 'Should not return cached response over duration', + sanitizeResources: false, + }, + async () => { + const time = new FakeTime() + const c = await caches.open('my-app') + await c.delete('http://localhost') + + let oneTimeWord = 'Hello Hono' + + const app = new Hono() + app.use( + '/', + cache({ + cacheName: 'my-app', + wait: true, + duration: 60, + }) + ) + app.get('/', (c) => { + const result = oneTimeWord + oneTimeWord = 'Not Found' + return c.text(result) + }) + + await app.request('http://localhost') + let res = await app.request('http://localhost') + + await time.tickAsync(59999) + + expect(await res.text()).toBe('Hello Hono') + expect(res.headers.get('Hono-Cache-Expires')).toBeNull() + expect(res).not.toBeNull() + expect(res.status).toBe(200) + + await time.tickAsync(1) + + res = await app.request('http://localhost') + expect(await res.text()).toBe('Not Found') + expect(res.headers.get('Hono-Cache-Expires')).toBeNull() + expect(res).not.toBeNull() + expect(res.status).toBe(200) + await c.delete('http://localhost') + } +) + +Deno.test( + { + name: 'Should return cached response if the response is immutable', + sanitizeResources: false, + }, + async () => { + const c = await caches.open('my-app') + await c.delete('http://localhost') + + let oneTime = true + let oneTimeWord = 'Hello Hono' + + const example = new Hono() + example.get('/', (c) => { + const result = oneTimeWord + oneTimeWord = 'Not Found' + return c.text(result) + }) + + const app = new Hono() + app.use( + '/', + cache({ + cacheName: 'my-app', + wait: true, + }) + ) + app.get('/', async (c) => { + const result = oneTime ? await fetch(`http://localhost:${server.addr.port}`) : 'Not Found' + oneTime = false + if (result instanceof Response) { + return result + } else { + return c.text('Not Found') + } + }) + + const server = Deno.serve({ port: 0 }, example.fetch) + + await app.request('http://localhost') + const res = await app.request('http://localhost') + expect(await res.text()).toBe('Hello Hono') + expect(res.headers.get('Hono-Cache-Expires')).toBeNull() + expect(res).not.toBeNull() + expect(res.status).toBe(200) + + await server.shutdown() + await c.delete('http://localhost') + } +) diff --git a/runtime-tests/deno/deno.json b/runtime_tests/deno/deno.json similarity index 89% rename from runtime-tests/deno/deno.json rename to runtime_tests/deno/deno.json index 240c5d2e4..012e7b7ee 100644 --- a/runtime-tests/deno/deno.json +++ b/runtime_tests/deno/deno.json @@ -13,6 +13,7 @@ ], "imports": { "@std/assert": "jsr:@std/assert@^1.0.3", + "@std/expect": "jsr:@std/expect@^1.0.2", "@std/testing": "jsr:@std/testing@^1.0.1", "hono/jsx/jsx-runtime": "../../src/jsx/jsx-runtime.ts" } diff --git a/runtime_tests/deno/deno.lock b/runtime_tests/deno/deno.lock new file mode 100644 index 000000000..537fe7fe1 --- /dev/null +++ b/runtime_tests/deno/deno.lock @@ -0,0 +1,54 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@std/assert@^1.0.3": "jsr:@std/assert@1.0.4", + "jsr:@std/assert@^1.0.4": "jsr:@std/assert@1.0.4", + "jsr:@std/async@^1.0.5": "jsr:@std/async@1.0.5", + "jsr:@std/data-structures@^1.0.2": "jsr:@std/data-structures@1.0.2", + "jsr:@std/expect@^1.0.2": "jsr:@std/expect@1.0.2", + "jsr:@std/internal@^1.0.3": "jsr:@std/internal@1.0.3", + "jsr:@std/testing@^1.0.1": "jsr:@std/testing@1.0.2" + }, + "jsr": { + "@std/assert@1.0.4": { + "integrity": "d4c767ea578e5bc09c15b6e503376003e5b2d1f4c0cdf08524a92101ff4d7b96", + "dependencies": [ + "jsr:@std/internal@^1.0.3" + ] + }, + "@std/async@1.0.5": { + "integrity": "31d68214bfbb31bd4c6022401d484e3964147c76c9220098baa703a39b6c2da6" + }, + "@std/data-structures@1.0.2": { + "integrity": "d586efced05ed91923514a192996d23673f32f695f7ebc37f0cc1e17928be600" + }, + "@std/expect@1.0.2": { + "integrity": "37d5162eb1d30505e09f77f3fabbf6cde1e81ed0e72d2371cddbce1173eb86b4", + "dependencies": [ + "jsr:@std/assert@^1.0.4", + "jsr:@std/internal@^1.0.3" + ] + }, + "@std/internal@1.0.3": { + "integrity": "208e9b94a3d5649bd880e9ca38b885ab7651ab5b5303a56ed25de4755fb7b11e" + }, + "@std/testing@1.0.2": { + "integrity": "9e8a7f4e26c219addabe7942d09c3450fa0a74e9662341961bc0ef502274dec3", + "dependencies": [ + "jsr:@std/assert@^1.0.4", + "jsr:@std/async@^1.0.5", + "jsr:@std/data-structures@^1.0.2" + ] + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "jsr:@std/assert@^1.0.3", + "jsr:@std/expect@^1.0.2", + "jsr:@std/testing@^1.0.1" + ] + } +} diff --git a/runtime-tests/deno/favicon.ico b/runtime_tests/deno/favicon.ico similarity index 100% rename from runtime-tests/deno/favicon.ico rename to runtime_tests/deno/favicon.ico diff --git a/runtime-tests/deno/hono.test.ts b/runtime_tests/deno/hono.test.ts similarity index 100% rename from runtime-tests/deno/hono.test.ts rename to runtime_tests/deno/hono.test.ts diff --git a/runtime-tests/deno/middleware.test.tsx b/runtime_tests/deno/middleware.test.tsx similarity index 96% rename from runtime-tests/deno/middleware.test.tsx rename to runtime_tests/deno/middleware.test.tsx index ed55dc80d..ad992fe9c 100644 --- a/runtime-tests/deno/middleware.test.tsx +++ b/runtime_tests/deno/middleware.test.tsx @@ -68,10 +68,10 @@ Deno.test('JSX middleware', async () => { Deno.test('Serve Static middleware', async () => { const app = new Hono() const onNotFound = spy(() => {}) - app.all('/favicon.ico', serveStatic({ path: './runtime-tests/deno/favicon.ico' })) + app.all('/favicon.ico', serveStatic({ path: './runtime_tests/deno/favicon.ico' })) app.all( '/favicon-notfound.ico', - serveStatic({ path: './runtime-tests/deno/favicon-notfound.ico', onNotFound }) + serveStatic({ path: './runtime_tests/deno/favicon-notfound.ico', onNotFound }) ) app.use('/favicon-notfound.ico', async (c, next) => { await next() @@ -81,7 +81,7 @@ Deno.test('Serve Static middleware', async () => { app.get( '/static/*', serveStatic({ - root: './runtime-tests/deno', + root: './runtime_tests/deno', onNotFound, }) ) @@ -89,7 +89,7 @@ Deno.test('Serve Static middleware', async () => { app.get( '/dot-static/*', serveStatic({ - root: './runtime-tests/deno', + root: './runtime_tests/deno', rewriteRequestPath: (path) => path.replace(/^\/dot-static/, './.static'), }) ) diff --git a/runtime-tests/deno/ssg.test.tsx b/runtime_tests/deno/ssg.test.tsx similarity index 100% rename from runtime-tests/deno/ssg.test.tsx rename to runtime_tests/deno/ssg.test.tsx diff --git a/runtime-tests/deno/static/download b/runtime_tests/deno/static/download similarity index 100% rename from runtime-tests/deno/static/download rename to runtime_tests/deno/static/download diff --git a/runtime-tests/deno/static/hello.world/index.html b/runtime_tests/deno/static/hello.world/index.html similarity index 100% rename from runtime-tests/deno/static/hello.world/index.html rename to runtime_tests/deno/static/hello.world/index.html diff --git a/runtime-tests/deno/static/helloworld/index.html b/runtime_tests/deno/static/helloworld/index.html similarity index 100% rename from runtime-tests/deno/static/helloworld/index.html rename to runtime_tests/deno/static/helloworld/index.html diff --git a/runtime-tests/deno/static/plain.txt b/runtime_tests/deno/static/plain.txt similarity index 100% rename from runtime-tests/deno/static/plain.txt rename to runtime_tests/deno/static/plain.txt diff --git a/runtime-tests/deno/stream.test.ts b/runtime_tests/deno/stream.test.ts similarity index 100% rename from runtime-tests/deno/stream.test.ts rename to runtime_tests/deno/stream.test.ts diff --git a/runtime-tests/fastly/index.test.ts b/runtime_tests/fastly/index.test.ts similarity index 100% rename from runtime-tests/fastly/index.test.ts rename to runtime_tests/fastly/index.test.ts diff --git a/runtime-tests/fastly/vitest.config.ts b/runtime_tests/fastly/vitest.config.ts similarity index 76% rename from runtime-tests/fastly/vitest.config.ts rename to runtime_tests/fastly/vitest.config.ts index d01a1a004..c0480b139 100644 --- a/runtime-tests/fastly/vitest.config.ts +++ b/runtime_tests/fastly/vitest.config.ts @@ -7,8 +7,8 @@ export default defineConfig({ plugins: [fastlyCompute()], test: { globals: true, - include: ['**/runtime-tests/fastly/**/(*.)+(test).+(ts|tsx)'], - exclude: ['**/runtime-tests/fastly/vitest.config.ts'], + include: ['**/runtime_tests/fastly/**/(*.)+(test).+(ts|tsx)'], + exclude: ['**/runtime_tests/fastly/vitest.config.ts'], coverage: { ...config.test?.coverage, reportsDirectory: './coverage/raw/runtime-fastly', diff --git a/runtime-tests/lambda-edge/index.test.ts b/runtime_tests/lambda-edge/index.test.ts similarity index 100% rename from runtime-tests/lambda-edge/index.test.ts rename to runtime_tests/lambda-edge/index.test.ts diff --git a/runtime-tests/lambda-edge/vitest.config.ts b/runtime_tests/lambda-edge/vitest.config.ts similarity index 72% rename from runtime-tests/lambda-edge/vitest.config.ts rename to runtime_tests/lambda-edge/vitest.config.ts index 5fd8bc9fe..105ac9505 100644 --- a/runtime-tests/lambda-edge/vitest.config.ts +++ b/runtime_tests/lambda-edge/vitest.config.ts @@ -8,8 +8,8 @@ export default defineConfig({ NAME: 'Node', }, globals: true, - include: ['**/runtime-tests/lambda-edge/**/*.+(ts|tsx|js)'], - exclude: ['**/runtime-tests/lambda-edge/vitest.config.ts'], + include: ['**/runtime_tests/lambda-edge/**/*.+(ts|tsx|js)'], + exclude: ['**/runtime_tests/lambda-edge/vitest.config.ts'], coverage: { ...config.test?.coverage, reportsDirectory: './coverage/raw/runtime-lambda-edge', diff --git a/runtime-tests/lambda/index.test.ts b/runtime_tests/lambda/index.test.ts similarity index 100% rename from runtime-tests/lambda/index.test.ts rename to runtime_tests/lambda/index.test.ts diff --git a/runtime-tests/lambda/mock.ts b/runtime_tests/lambda/mock.ts similarity index 100% rename from runtime-tests/lambda/mock.ts rename to runtime_tests/lambda/mock.ts diff --git a/runtime-tests/lambda/stream-mock.ts b/runtime_tests/lambda/stream-mock.ts similarity index 100% rename from runtime-tests/lambda/stream-mock.ts rename to runtime_tests/lambda/stream-mock.ts diff --git a/runtime-tests/lambda/stream.test.ts b/runtime_tests/lambda/stream.test.ts similarity index 100% rename from runtime-tests/lambda/stream.test.ts rename to runtime_tests/lambda/stream.test.ts diff --git a/runtime-tests/lambda/vitest.config.ts b/runtime_tests/lambda/vitest.config.ts similarity index 64% rename from runtime-tests/lambda/vitest.config.ts rename to runtime_tests/lambda/vitest.config.ts index 8cee5a8ec..01caae52b 100644 --- a/runtime-tests/lambda/vitest.config.ts +++ b/runtime_tests/lambda/vitest.config.ts @@ -8,11 +8,11 @@ export default defineConfig({ NAME: 'Node', }, globals: true, - include: ['**/runtime-tests/lambda/**/*.+(ts|tsx|js)'], + include: ['**/runtime_tests/lambda/**/*.+(ts|tsx|js)'], exclude: [ - '**/runtime-tests/lambda/vitest.config.ts', - '**/runtime-tests/lambda/mock.ts', - '**/runtime-tests/lambda/stream-mock.ts', + '**/runtime_tests/lambda/vitest.config.ts', + '**/runtime_tests/lambda/mock.ts', + '**/runtime_tests/lambda/stream-mock.ts', ], coverage: { ...config.test?.coverage, diff --git a/runtime-tests/node/index.test.ts b/runtime_tests/node/index.test.ts similarity index 100% rename from runtime-tests/node/index.test.ts rename to runtime_tests/node/index.test.ts diff --git a/runtime-tests/node/vitest.config.ts b/runtime_tests/node/vitest.config.ts similarity index 74% rename from runtime-tests/node/vitest.config.ts rename to runtime_tests/node/vitest.config.ts index 4cb6deed6..5387c11fd 100644 --- a/runtime-tests/node/vitest.config.ts +++ b/runtime_tests/node/vitest.config.ts @@ -8,8 +8,8 @@ export default defineConfig({ NAME: 'Node', }, globals: true, - include: ['**/runtime-tests/node/**/*.+(ts|tsx|js)'], - exclude: ['**/runtime-tests/node/vitest.config.ts'], + include: ['**/runtime_tests/node/**/*.+(ts|tsx|js)'], + exclude: ['**/runtime_tests/node/vitest.config.ts'], coverage: { ...config.test?.coverage, reportsDirectory: './coverage/raw/runtime-node', diff --git a/runtime-tests/tsconfig.json b/runtime_tests/tsconfig.json similarity index 100% rename from runtime-tests/tsconfig.json rename to runtime_tests/tsconfig.json diff --git a/runtime-tests/workerd/index.test.ts b/runtime_tests/workerd/index.test.ts similarity index 93% rename from runtime-tests/workerd/index.test.ts rename to runtime_tests/workerd/index.test.ts index 2173ce6ae..8a28daa9d 100644 --- a/runtime-tests/workerd/index.test.ts +++ b/runtime_tests/workerd/index.test.ts @@ -6,7 +6,7 @@ describe('workerd', () => { let worker: UnstableDevWorker beforeAll(async () => { - worker = await unstable_dev('./runtime-tests/workerd/index.ts', { + worker = await unstable_dev('./runtime_tests/workerd/index.ts', { vars: { NAME: 'Hono', }, @@ -35,7 +35,7 @@ describe('workerd with WebSocket', () => { // worker.fetch does not support WebSocket: // https://github.com/cloudflare/workers-sdk/issues/4573#issuecomment-1850420973 it('Should handle the WebSocket connection correctly', async () => { - const worker = await unstable_dev('./runtime-tests/workerd/index.ts', { + const worker = await unstable_dev('./runtime_tests/workerd/index.ts', { experimental: { disableExperimentalWarning: true }, }) const ws = new WebSocket(`ws://${worker.address}:${worker.port}/ws`) diff --git a/runtime-tests/workerd/index.ts b/runtime_tests/workerd/index.ts similarity index 100% rename from runtime-tests/workerd/index.ts rename to runtime_tests/workerd/index.ts diff --git a/runtime-tests/workerd/vitest.config.ts b/runtime_tests/workerd/vitest.config.ts similarity index 71% rename from runtime-tests/workerd/vitest.config.ts rename to runtime_tests/workerd/vitest.config.ts index c82861be7..43410a1b4 100644 --- a/runtime-tests/workerd/vitest.config.ts +++ b/runtime_tests/workerd/vitest.config.ts @@ -5,8 +5,8 @@ import config from '../../vitest.config' export default defineConfig({ test: { globals: true, - include: ['**/runtime-tests/workerd/**/(*.)+(test).+(ts|tsx)'], - exclude: ['**/runtime-tests/workerd/vitest.config.ts'], + include: ['**/runtime_tests/workerd/**/(*.)+(test).+(ts|tsx)'], + exclude: ['**/runtime_tests/workerd/vitest.config.ts'], coverage: { ...config.test?.coverage, reportsDirectory: './coverage/raw/runtime-workerd', diff --git a/src/middleware/cache/index.ts b/src/middleware/cache/index.ts index f7eecf6e4..749dfe463 100644 --- a/src/middleware/cache/index.ts +++ b/src/middleware/cache/index.ts @@ -16,6 +16,7 @@ import type { MiddlewareHandler } from '../../types' * @param {boolean} [options.wait=false] - A boolean indicating if Hono should wait for the Promise of the `cache.put` function to resolve before continuing with the request. Required to be true for the Deno environment. * @param {string} [options.cacheControl] - A string of directives for the `Cache-Control` header. * @param {string | string[]} [options.vary] - Sets the `Vary` header in the response. If the original response header already contains a `Vary` header, the values are merged, removing any duplicates. + * @param {number} [options.duration] - A number of seconds to cache the response. * @param {Function} [options.keyGenerator] - Generates keys for every request in the `cacheName` store. This can be used to cache data based on request parameters or context parameters. * @returns {MiddlewareHandler} The middleware handler function. * @throws {Error} If the `vary` option includes "*". @@ -36,6 +37,7 @@ export const cache = (options: { wait?: boolean cacheControl?: string vary?: string | string[] + duration?: number keyGenerator?: (c: Context) => Promise | string }): MiddlewareHandler => { if (!globalThis.caches) { @@ -72,7 +74,9 @@ export const cache = (options: { let [name, value] = directive.trim().split('=', 2) name = name.toLowerCase() if (!existingDirectives.includes(name)) { - c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { append: true }) + c.header('Cache-Control', `${name}${value ? `=${value}` : ''}`, { + append: true, + }) } } } @@ -98,6 +102,27 @@ export const cache = (options: { } } + const internalCacheExpires = 'Hono-Cache-Expires' + + // Create a new response for avoiding the immutable response. + const createNewResponse = (res: Response, callbackFn: (res: Response) => void): Response => { + try { + callbackFn(res) + } catch (e) { + if (e instanceof TypeError && e.message.includes('immutable')) { + // `res` is immutable (probably a response from a fetch API), so retry with a new response. + res = new Response(res.body, { + headers: res.headers, + status: res.status, + }) + callbackFn(res) + } else { + throw e + } + } + return res + } + return async function cache(c, next) { let key = c.req.url if (options.keyGenerator) { @@ -109,7 +134,15 @@ export const cache = (options: { const cache = await caches.open(cacheName) const response = await cache.match(key) if (response) { - return new Response(response.body, response) + if (typeof options.duration === 'number') { + const expires = Number(response.headers.get(internalCacheExpires)) + if (expires > Date.now()) { + const res = createNewResponse(response, (res) => res.headers.delete(internalCacheExpires)) + return res + } + } else { + return response + } } await next() @@ -117,7 +150,12 @@ export const cache = (options: { return } addHeader(c) - const res = c.res.clone() + let res = c.res.clone() + + if (options.duration && options.duration > 0) { + const expiresTime = String(Date.now() + options.duration * 1000) + res = createNewResponse(res, (res) => res.headers.set(internalCacheExpires, expiresTime)) + } if (options.wait) { await cache.put(key, res) } else { diff --git a/src/middleware/serve-static/index.test.ts b/src/middleware/serve-static/index.test.ts index 3f0c493f6..585008ca7 100644 --- a/src/middleware/serve-static/index.test.ts +++ b/src/middleware/serve-static/index.test.ts @@ -143,7 +143,6 @@ describe('Serve Static Middleware', () => { expect(res.status).toBe(200) expect(res.headers.get('Content-Encoding')).toBe('br') expect(res.headers.get('Vary')).toBe('Accept-Encoding') - expect(res.headers.get('Content-Type')).toBe('application/octet-stream') expect(await res.text()).toBe('Hello in static/hello.unknown.br') }) diff --git a/src/middleware/serve-static/index.ts b/src/middleware/serve-static/index.ts index d9cdfbc06..6c45d61ac 100644 --- a/src/middleware/serve-static/index.ts +++ b/src/middleware/serve-static/index.ts @@ -99,8 +99,13 @@ export const serveStatic = ( } if (content) { - const mimeType = (options.mimes && getMimeType(path, options.mimes)) || getMimeType(path) - c.header('Content-Type', mimeType || 'application/octet-stream') + const mimeType = options.mimes + ? getMimeType(path, options.mimes) ?? getMimeType(path) + : getMimeType(path) + + if (mimeType) { + c.header('Content-Type', mimeType) + } if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) { const acceptEncodingSet = new Set( diff --git a/vitest.config.ts b/vitest.config.ts index 8c968fdee..26ce5de6d 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ exclude: [ ...(configDefaults.coverage.exclude ?? []), 'benchmarks', - 'runtime-tests', + 'runtime_tests', 'build.ts', 'src/test-utils',