Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .changeset/advanced-routing-fetchfile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'astro': patch
---

Adds `fetchFile` option to `experimental.advancedRouting` to customize or disable the entrypoint file

```js
export default defineConfig({
experimental: {
advancedRouting: {
fetchFile: 'fetch.ts',
},
},
});
```
5 changes: 5 additions & 0 deletions .changeset/advanced-routing-hono-cache.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes Hono `cache()` middleware to follow the standard wrapper pattern
13 changes: 13 additions & 0 deletions .changeset/advanced-routing-providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'astro': patch
---

Adds `App.Providers` interface for typing custom context providers on `Astro` and `ctx`

```ts
declare namespace App {
interface Providers {
oauth: import('./lib/oauth').OAuthSession;
}
}
```
10 changes: 10 additions & 0 deletions .changeset/advanced-routing-state-response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'astro': patch
---

Adds `FetchState.response` property, set automatically after `pages()` or `middleware()` completes

```ts
const response = await middleware(state, (s) => pages(s));
console.log(state.response === response); // true
```
15 changes: 15 additions & 0 deletions .changeset/advanced-routing-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
'astro': patch
---

Adds `Fetchable` type export for typing the advanced routing entrypoint

```ts
import type { Fetchable } from 'astro';

export default {
async fetch(request) {
return new Response('ok');
},
} satisfies Fetchable;
```
7 changes: 6 additions & 1 deletion packages/astro/src/core/config/schemas/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,12 @@ export const AstroConfigSchema = z.object({
experimental: z
.strictObject({
advancedRouting: z
.boolean()
.union([
z.boolean(),
z.strictObject({
fetchFile: z.string().nullable().optional().default('app'),
}),
])
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.advancedRouting),
clientPrerender: z
Expand Down
7 changes: 7 additions & 0 deletions packages/astro/src/core/fetch/fetch-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export interface AstroFetchState {
readonly locals: App.Locals;
/** Route params derived from routeData + pathname. */
readonly params: Params | undefined;
/** The `Response` produced by handlers, if any. Set after rendering. */
response: Response | undefined;
/** Default HTTP status for the rendered response. */
status: number;

Expand Down Expand Up @@ -168,6 +170,11 @@ export class FetchState implements AstroFetchState {
* allocate an empty object per request.
*/
slots: Record<string, any> | undefined;
/**
* The `Response` produced by handlers, if any. Set after page
* rendering or middleware completes.
*/
response: Response | undefined;
/**
* Default HTTP status for the rendered response. Callers override
* before rendering runs (e.g. `AstroHandler` sets this from
Expand Down
8 changes: 8 additions & 0 deletions packages/astro/src/core/fetch/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
/**
* Public `astro/fetch` API.
*
* Every exported function here is a thin wrapper that resolves the
* correct handler instance and delegates to it. **Do not add logic
* here** — keep behaviour inside the handler modules so it stays
* unit-testable without the virtual-module wiring.
*/
import { ActionHandler } from '../../actions/handler.js';
import type { BaseApp } from '../app/base.js';
import type { Pipeline } from '../base-pipeline.js';
Expand Down
20 changes: 20 additions & 0 deletions packages/astro/src/core/fetch/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,23 @@
* lets handlers compose easily with other middleware systems later.
*/
export type FetchHandler = (request: Request) => Promise<Response>;

/**
* An object with a `fetch` method that handles incoming requests.
* This is the shape expected by `src/app.ts` and aligns with the
* convention used by Cloudflare Workers, Bun, and Hono.
*
* @example
* ```ts
* import type { Fetchable } from 'astro';
*
* export default {
* async fetch(request) {
* return new Response('ok');
* }
* } satisfies Fetchable;
* ```
*/
export interface Fetchable {
fetch(request: Request): Response | Promise<Response>;
}
24 changes: 16 additions & 8 deletions packages/astro/src/core/fetch/vite-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ import { ASTRO_VITE_ENVIRONMENT_NAMES } from '../constants.js';
*/
const FETCHABLE_MODULE_ID = 'virtual:astro:fetchable';
const FETCHABLE_RESOLVED_MODULE_ID = '\0' + FETCHABLE_MODULE_ID;
// Segment under `srcDir` to probe for the user's handler module.
// Default segment under `srcDir` to probe for the user's handler module.
// Matches how `vitePluginMiddleware` resolves `src/middleware`.
const APP_PATH_SEGMENT_NAME = 'app';
const DEFAULT_FETCH_FILE = 'app';

export function vitePluginFetchable({ settings }: { settings: AstroSettings }): VitePlugin {
let resolvedUserAppId: string | undefined;
let userAppPresent = false;
const advancedRoutingEnabled = settings.config.experimental.advancedRouting;
const advancedRoutingConfig = settings.config.experimental.advancedRouting;
const advancedRoutingEnabled = !!advancedRoutingConfig;
const fetchFile = (typeof advancedRoutingConfig === 'object' ? advancedRoutingConfig.fetchFile : undefined) ?? DEFAULT_FETCH_FILE;
// When fetchFile is null the feature is explicitly disabled.
const fetchFileDisabled = typeof advancedRoutingConfig === 'object' && advancedRoutingConfig.fetchFile === null;

const normalizedSrcDir = viteNormalizePath(fileURLToPath(settings.config.srcDir));

Expand All @@ -44,9 +48,9 @@ export function vitePluginFetchable({ settings }: { settings: AstroSettings }):
server.watcher.on('change', (path) => {
const normalizedPath = viteNormalizePath(path);
if (!normalizedPath.startsWith(normalizedSrcDir)) return;
const relativePath = normalizedPath.slice(normalizedSrcDir.length);
// Dot-prefix guard: match `app.ts` but not e.g. `app-utils.ts`.
if (!relativePath.startsWith(`${APP_PATH_SEGMENT_NAME}.`)) return;
const relativePath = normalizedPath.slice(normalizedSrcDir.length);
// Dot-prefix guard: match `app.ts` but not e.g. `app-utils.ts`.
if (!relativePath.startsWith(`${fetchFile}.`)) return;

for (const name of [
ASTRO_VITE_ENVIRONMENT_NAMES.ssr,
Expand All @@ -66,8 +70,12 @@ export function vitePluginFetchable({ settings }: { settings: AstroSettings }):
filter: {
id: new RegExp(`^${FETCHABLE_MODULE_ID}$`),
},
async handler() {
const resolved = await this.resolve(`${normalizedSrcDir}${APP_PATH_SEGMENT_NAME}`);
async handler() {
if (fetchFileDisabled) {
userAppPresent = false;
return FETCHABLE_RESOLVED_MODULE_ID;
}
const resolved = await this.resolve(`${normalizedSrcDir}${fetchFile}`);
userAppPresent = advancedRoutingEnabled && !!resolved;
resolvedUserAppId = resolved?.id;
return FETCHABLE_RESOLVED_MODULE_ID;
Expand Down
9 changes: 6 additions & 3 deletions packages/astro/src/core/hono/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ export function sessions(): HonoMiddlewareHandler {
};
}

export function cache(next: () => Promise<Response>): HonoMiddlewareHandler {
return async (context, _honoNext) => {
return fetchCache(getFetchState(context), next);
export function cache(): HonoMiddlewareHandler {
return async (context, honoNext) => {
return fetchCache(getFetchState(context), async () => {
await honoNext();
return context.res;
});
};
}

Expand Down
4 changes: 3 additions & 1 deletion packages/astro/src/core/middleware/astro-middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ export class AstroMiddleware {
const composed = sequence(...pipeline.internalMiddleware, pipelineMiddleware);
response = await callMiddleware(composed, apiContext, next);
}
return this.#finalize(state, response);
response = this.#finalize(state, response);
state.response = response;
return response;
}

#finalize(state: FetchState, response: Response): Response {
Expand Down
15 changes: 8 additions & 7 deletions packages/astro/src/core/pages/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,13 @@ export class PagesHandler {
return new Response(null, { status: 500, headers: { [ROUTE_TYPE_HEADER]: 'fallback' } });
}
}
// We need to merge the cookies from the response back into the cookies
// because they may need to be passed along from a rewrite.
const responseCookies = getCookiesFromResponse(response);
if (responseCookies) {
state.cookies!.merge(responseCookies);
}
return response;
// We need to merge the cookies from the response back into the cookies
// because they may need to be passed along from a rewrite.
const responseCookies = getCookiesFromResponse(response);
if (responseCookies) {
state.cookies!.merge(responseCookies);
}
state.response = response;
return response;
}
}
23 changes: 20 additions & 3 deletions packages/astro/src/types/public/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2793,21 +2793,38 @@ export interface AstroUserConfig<
experimental?: {
/**
* @name experimental.advancedRouting
* @type {boolean}
* @type {boolean | object}
* @default `false`
* @description
* Enables `src/app.ts` as an advanced routing entrypoint, allowing you to
* compose Astro's request pipeline with the Web Fetch standard or your own Hono middleware.
*
* Pass `true` to enable with default settings, or an object to customize:
*
* ```js
* export default defineConfig({
* experimental: {
* advancedRouting: true,
* advancedRouting: {
* fetchFile: 'fetch.ts',
* },
* },
* });
* ```
*/
advancedRouting?: boolean;
advancedRouting?: boolean | {
/**
* @name experimental.advancedRouting.fetchFile
* @type {string | null}
* @default 'app'
* @description
*
* Customizes the file used as the advanced routing entrypoint inside `srcDir`.
* Defaults to `'app'`, meaning Astro looks for `src/app.ts`.
*
* If you already have a `src/app.ts` file in use for other purposes, define a different filename or set the value to `null` to disable the entrypoint.
*/
Comment thread
matthewp marked this conversation as resolved.
fetchFile?: string | null;
};

/**
*
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/types/public/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export interface AstroGlobal<
export interface APIContext<
Props extends Record<string, any> = Record<string, any>,
Params extends Record<string, string | undefined> = Record<string, string | undefined>,
> {
> extends App.Providers {
/**
* The site provided in the astro config, parsed as an instance of `URL`, without base.
* `undefined` if the site is not provided in the config.
Expand Down
19 changes: 19 additions & 0 deletions packages/astro/src/types/public/extendables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@ declare global {
* Optionally type the data stored in the session
*/
export interface SessionData {}

/**
* Declare custom context providers to get typed access on `Astro` and `ctx`.
* Libraries and users register providers via `state.provide(key, { create, finalize? })`,
* and the corresponding types are declared here using module augmentation.
*
* Built-in providers like `session` are already typed by Astro and don't
* need to be declared here.
*
* @example
* ```ts
* declare namespace App {
* interface Providers {
* oauth: import('./lib/oauth').OAuthSession;
* }
* }
* ```
*/
export interface Providers {}
}

namespace Astro {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/types/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type { AstroIntegrationLogger } from '../../core/logger/core.js';
export type { AstroSession } from '../../core/session/runtime.js';
export type { ToolbarServerHelpers } from '../../runtime/client/dev-toolbar/helpers.js';
export type { AstroEnvironmentNames } from '../../core/constants.js';
export type { Fetchable } from '../../core/fetch/types.js';
export type { SessionDriver, SessionDriverConfig } from '../../core/session/types.js';
export type {
CacheProvider,
Expand Down
Loading
Loading