diff --git a/.changeset/expose-get-fetch-state.md b/.changeset/expose-get-fetch-state.md new file mode 100644 index 000000000000..7863eb5bc95c --- /dev/null +++ b/.changeset/expose-get-fetch-state.md @@ -0,0 +1,24 @@ +--- +'astro': minor +--- + +Exposes `getFetchState()` from `astro/hono` as a public API + +The `getFetchState()` function retrieves or lazily creates a `FetchState` from a Hono context object. This allows third-party packages to build Hono middleware that interacts with Astro's per-request state, giving the `astro/hono` API the same extensibility as `astro/fetch`. + +```ts +import { Hono } from 'hono'; +import { getFetchState, pages } from 'astro/hono'; + +const app = new Hono(); + +app.use(async (context, next) => { + const state = getFetchState(context); + state.locals.message = 'Hello from custom middleware'; + await next(); +}); + +app.use(pages()); + +export default app; +``` diff --git a/packages/astro/src/core/hono/index.ts b/packages/astro/src/core/hono/index.ts index 454626c78f23..0e78d070b077 100644 --- a/packages/astro/src/core/hono/index.ts +++ b/packages/astro/src/core/hono/index.ts @@ -30,7 +30,7 @@ type HonoMiddlewareHandler = ( next: () => Promise, ) => Promise; -function getFetchState(context: HonoContextLike): FetchState { +export function getFetchState(context: HonoContextLike): FetchState { const state = context.get?.(FETCH_STATE_KEY) as FetchState | undefined; if (state) { return state; diff --git a/packages/astro/test/units/hono/index.test.ts b/packages/astro/test/units/hono/index.test.ts index a8c2c65634eb..13f187e73527 100644 --- a/packages/astro/test/units/hono/index.test.ts +++ b/packages/astro/test/units/hono/index.test.ts @@ -3,7 +3,7 @@ import { describe, it } from 'node:test'; import { Hono } from 'hono'; import { FetchState } from '../../../dist/core/fetch/fetch-state.js'; import { appSymbol } from '../../../dist/core/constants.js'; -import { astro } from '../../../dist/core/hono/index.js'; +import { astro, getFetchState } from '../../../dist/core/hono/index.js'; import { createComponent, render } from '../../../dist/runtime/server/index.js'; import { createPage, createTestApp } from '../mocks.ts'; @@ -55,3 +55,55 @@ describe('astro() Hono middleware', () => { assert.match(await response.text(), /

from stashed FetchState<\/h1>/); }); }); + +describe('getFetchState()', () => { + it('creates a new FetchState from the Hono context', async () => { + const astroApp = createTestApp([createPage(page, { route: '/' })]); + const hono = createHonoApp(astroApp); + let capturedState: FetchState | undefined; + hono.use(async (context, next) => { + capturedState = getFetchState(context); + await next(); + }); + hono.use(astro()); + + const response = await hono.fetch(new Request('http://example.com/')); + + assert.equal(response.status, 200); + assert.ok(capturedState instanceof FetchState, 'getFetchState should return a FetchState'); + }); + + it('returns the same FetchState on subsequent calls', async () => { + const astroApp = createTestApp([createPage(page, { route: '/' })]); + const hono = createHonoApp(astroApp); + let firstState: FetchState | undefined; + let secondState: FetchState | undefined; + hono.use(async (context, next) => { + firstState = getFetchState(context); + secondState = getFetchState(context); + await next(); + }); + hono.use(astro()); + + await hono.fetch(new Request('http://example.com/')); + + assert.ok(firstState); + assert.equal(firstState, secondState, 'getFetchState should return the same instance'); + }); + + it('allows custom middleware to set locals via getFetchState', async () => { + const astroApp = createTestApp([createPage(page, { route: '/' })]); + const hono = createHonoApp(astroApp); + hono.use(async (context, next) => { + const state = getFetchState(context); + state.locals = { message: 'set via getFetchState' } as any; + await next(); + }); + hono.use(astro()); + + const response = await hono.fetch(new Request('http://example.com/')); + + assert.equal(response.status, 200); + assert.match(await response.text(), /

set via getFetchState<\/h1>/); + }); +});