Skip to content

Commit

Permalink
Astro integration
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Jul 10, 2024
1 parent 1711224 commit 5b347b2
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 3 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"activitypub",
"aitertools",
"apidoc",
"astro",
"bccs",
"btos",
"callouts",
Expand Down
9 changes: 9 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,17 +96,26 @@ To be released.
loader that throws an error when the given URL is not an HTTP or HTTPS
URL or refers to a private network address.

- Added `@fedify/fedify/x/astro` module for integrating with [Astro]
middleware. [[#50]]

- Added `createMiddleware()` function.
- Added `createFetchOptions()` function.
- Added `ContextDataFactory` type.

- Added more log messages using the [LogTape] library. Currently the below
logger categories are used:

- `["fedify", "federation", "queue"]`

[#50]: https://github.com/dahlia/fedify/issues/50
[#53]: https://github.com/dahlia/fedify/issues/53
[#66]: https://github.com/dahlia/fedify/issues/66
[#70]: https://github.com/dahlia/fedify/issues/70
[#81]: https://github.com/dahlia/fedify/issues/81
[#85]: https://github.com/dahlia/fedify/issues/85
[#92]: https://github.com/dahlia/fedify/pull/92
[Astro]: https://astro.build/


Version 0.11.2
Expand Down
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"./sig": "./sig/mod.ts",
"./vocab": "./vocab/mod.ts",
"./webfinger": "./webfinger/mod.ts",
"./x/astro": "./x/astro.ts",
"./x/denokv": "./x/denokv.ts",
"./x/fresh": "./x/fresh.ts",
"./x/hono": "./x/hono.ts"
Expand Down
140 changes: 140 additions & 0 deletions x/astro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Fedify with Astro
* =================
*
* This module contains some utilities for integrating Fedify with
* the [Astro] framework.
*
* [Astro]: https://astro.build/
*
* @module
* @since 0.12.0
*/
import type {
Federation,
FederationFetchOptions,
} from "../federation/middleware.ts";

interface AstroContext {
request: Request;
}
type RewritePayload = string | URL | Request;
type MiddlewareNext = (
rewritePayload?: RewritePayload,
) => Promise<Response>;
type MiddlewareHandler<TAstroContext extends AstroContext> = (
context: TAstroContext,
next: MiddlewareNext,
) => Promise<Response> | Response | Promise<void> | void;

/**
* Create options for the {@link Federation.fetch} method to integrate with
* Astro.
*
* @example src/middleware.ts
* ``` typescript
* import { defineMiddleware } from "astro:middleware";
* import { federation } from "./federation"; // Import the `Federation` object
*
* export const onRequest = defineMiddleware((context, next) => {
* return federation.fetch(context.request, {
* contextData: undefined,
* ...createFetchOptions(context, next),
* });
* });
* ```
*
* @typeParam TAstroContext A type of the Astro context.
* @param context An Astro context.
* @param next A function to call the next middleware.
* @returns Options for the {@link Federation.fetch} method.
* @since 0.12.0
*/
export function createFetchOptions<TAstroContext extends AstroContext>(
_context: TAstroContext,
next: MiddlewareNext,
): Omit<FederationFetchOptions<void>, "contextData"> {
return {
// If the `federation` object finds a request not responsible for it
// (i.e., not a federation-related request), it will call the `next`
// provided by the Astro framework to continue the request handling
// by Astro:
onNotFound: next,

// Similar to `onNotFound`, but slightly more tricky one.
// When the `federation` object finds a request not acceptable type-wise
// (i.e., a user-agent doesn't want JSON-LD), it will call the `next`
// provided by the Astro framework so that it renders HTML if there's some
// page. Otherwise, it will simply return a 406 Not Acceptable response.
// This kind of trick enables the Fedify and Astro to share the same routes
// and they do content negotiation depending on `Accept` header:
async onNotAcceptable(_request: Request) {
const response = await next();
if (response.status !== 404) return response;
return new Response("Not acceptable", {
status: 406,
headers: {
"Content-Type": "text/plain",
Vary: "Accept",
},
});
},
};
}

/**
* The factory function to create a context data for
* the {@link Federation.fetch}.
*
* @typeParam TContextData A type of the context data.
* @typeParam TAstroContext A type of the Astro context.
* @param context An Astro context.
* @returns The context data for the {@link Federation.fetch}.
* @since 0.12.0
*/
export type ContextDataFactory<
TContextData,
TAstroContext extends AstroContext,
> = (
context: TAstroContext,
) => TContextData | Promise<TContextData>;

/**
* Create an Astro middleware handler to integrate with the {@link Federation}
* object.
*
* @example src/middleware.ts
* ``` typescript
* import type { MiddlewareHandler } from "astro";
* import { federation } from "./federation"; // Import the `Federation` object
*
* export const onRequest: MiddlewareHandler = createMiddleware(
* federation,
* (astroContext) => "context data",
* );
* ```
*
* @typeParam TContextData A type of the context data for the {@link Federation}
* object.
* @typeParam TAstroContext A type of the Astro context.
* @param federation A {@link Federation} object to integrate with Astro.
* @param contextDataFactory A factory function to create a context data for the
* {@link Federation} object.
* @returns An Astro middleware handler.
* @since 0.12.0
*/
export function createMiddleware<
TContextData,
TAstroContext extends AstroContext,
>(
federation: Federation<TContextData>,
contextDataFactory: ContextDataFactory<TContextData, TAstroContext>,
): MiddlewareHandler<TAstroContext> {
return async (context, next) => {
const contextData = await contextDataFactory(context);
return await federation.fetch(context.request, {
contextData,
...createFetchOptions(context, next),
});
};
}
7 changes: 4 additions & 3 deletions x/fresh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ interface FreshContext {
}

/**
* Create options for the `federation` object to integrate with Fresh.
* Create options for the {@link Federation.fetch} method to integrate with
* Fresh.
*
* @example _middleware.ts
* ``` typescript
Expand Down Expand Up @@ -88,6 +89,7 @@ export function integrateFetchOptions(
* @param createContextData A function to create a context data for the
* {@link Federation} object.
* @returns A Fresh middleware handler.
* @since 0.4.0
*/
export function integrateHandler<
TContextData,
Expand All @@ -103,8 +105,7 @@ export function integrateHandler<
request: Request,
context: TFreshContext,
): Promise<Response> => {
let contextData = createContextData(request, context);
if (contextData instanceof Promise) contextData = await contextData;
const contextData = await createContextData(request, context);
return await federation.fetch(request, {
contextData,
...integrateFetchOptions(context),
Expand Down

0 comments on commit 5b347b2

Please sign in to comment.