Skip to content

Commit

Permalink
feat: make ctx.platform typed (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
cyco130 authored Jul 20, 2023
1 parent fbdd6fa commit bec153b
Show file tree
Hide file tree
Showing 17 changed files with 279 additions and 145 deletions.
15 changes: 15 additions & 0 deletions packages/adapter/adapter-bun/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ You can leave out the `staticDir` option if you don't want to serve static asset

The remaining options (`port`, `hostname` etc.) are passed to [Bun.serve](https://github.com/oven-sh/bun#bunserve---fast-http-server).

## `context.platform`

```ts
export interface BunPlatformInfo {
/** Platform name */
name: "bun";
/** Bun server instance */
server: Server;
}
```

## Environment variables

The `ctx.env()` function is implemented using `process.env`.

## Limitations

Bun support is preliminary and Bun itself is in early development:
Expand Down
21 changes: 13 additions & 8 deletions packages/adapter/adapter-bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@
import fs from "node:fs";
import path from "node:path";
import type { AdapterRequestContext, HattipHandler } from "@hattip/core";
import type { Serve as BunServeOptions } from "bun";
import type { Serve, Server } from "bun";

export type { BunServeOptions };

export type BunAdapterOptions = Omit<BunServeOptions, "fetch" | "error"> & {
export type BunAdapterOptions = Omit<Serve, "fetch" | "error"> & {
staticDir?: string;
trustProxy?: boolean;
};

export interface BunPlatformInfo {
/** Platform name */
name: "bun";
/** Bun server instance */
server: Server;
}

export default function bunAdapter(
handler: HattipHandler,
handler: HattipHandler<BunPlatformInfo>,
options: BunAdapterOptions = {},
): BunServeOptions {
): Serve {
const { staticDir, trustProxy, ...remaingOptions } = options;

let staticFiles: Set<string>;
Expand Down Expand Up @@ -42,7 +47,7 @@ export default function bunAdapter(
}
}

const context: AdapterRequestContext = {
const context: AdapterRequestContext<BunPlatformInfo> = {
request,
// TODO: How to get the IP address when not behind a proxy?
ip: trustProxy
Expand All @@ -56,7 +61,7 @@ export default function bunAdapter(
waitUntil() {
// No op
},
platform: { name: "bun" },
platform: { name: "bun", server: this },
env(variable: string) {
return process.env[variable];
},
Expand Down
18 changes: 16 additions & 2 deletions packages/adapter/adapter-cloudflare-workers/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,22 @@ If you don't need to serve static files, you can import the adapter from `@hatti

## `context.platform`

This adapter's platform context contains `env` and `context` properties as passed to the worker's `fetch` function.
```ts
export interface CloudflareWorkersPlatformInfo {
/** Platform name */
name: "cloudflare-workers";
/**
* Bindings for secrets, environment variables, and other resources like
* KV namespaces etc.
*/
env: unknown;
/**
* Execution context
*/
context: ExecutionContext;
}
```

## Environment variables

The `ctx.env()` function only returns bindings with a string value. Other bindings like KV or D1 will return `undefined`. You should use `ctx.platform.env` to access them instead.
The `ctx.env()` function only returns bindings with a string value. Such bindings correspond to [secrets and environment variables](https://developers.cloudflare.com/workers/platform/environment-variables). [Other bindings](https://developers.cloudflare.com/workers/configuration/bindings) like KV or D1 will return `undefined`. You should use `ctx.platform.env` to access them instead.
13 changes: 12 additions & 1 deletion packages/adapter/adapter-cloudflare-workers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,24 @@ import manifestText from "__STATIC_CONTENT_MANIFEST";
const manifest = JSON.parse(manifestText);

export interface CloudflareWorkersPlatformInfo {
/** Platform name */
name: "cloudflare-workers";
/**
* Bindings for secrets, environment variables, and other resources like
* KV namespaces etc.
* @see https://developers.cloudflare.com/workers/platform/environment-variables
* @see https://developers.cloudflare.com/workers/configuration/bindings
*/
env: unknown;
/**
* Execution context
* @see https://developers.cloudflare.com/workers/runtime-apis/fetch-event/#parameters
*/
context: ExecutionContext;
}

export default function cloudflareWorkersAdapter(
handler: HattipHandler,
handler: HattipHandler<CloudflareWorkersPlatformInfo>,
): ExportedHandlerFetchHandler {
return async function fetchHandler(request, env, ctx) {
if (request.method === "GET" || request.method === "HEAD") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { CloudflareWorkersPlatformInfo } from ".";
export type { CloudflareWorkersPlatformInfo };

export default function cloudflareWorkersAdapter(
handler: HattipHandler,
handler: HattipHandler<CloudflareWorkersPlatformInfo>,
): ExportedHandlerFetchHandler {
return async function fetchHandler(request, env, ctx) {
const context: AdapterRequestContext<CloudflareWorkersPlatformInfo> = {
Expand Down
9 changes: 8 additions & 1 deletion packages/adapter/adapter-fastly/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ export default fastlyAdapter(async (ctx) => {

## `context.platform`

This adapter's platform context contains a `client` object, which is [Fastly FetchEvent.client](https://js-compute-reference-docs.edgecompute.app/docs/globals/FetchEvent/#instance-properties).
```ts
export interface FastlyPlatformInfo {
/** Platform name */
name: "fastly-compute";
/** Event object */
event: FetchEvent;
}
```

## Limitations

Expand Down
22 changes: 7 additions & 15 deletions packages/adapter/adapter-fastly/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,23 @@ import type { AdapterRequestContext, HattipHandler } from "@hattip/core";
import { env } from "fastly:env";

export interface FastlyPlatformInfo {
/** Platform name */
name: "fastly-compute";
client: ClientInfo;
/** Event object */
event: FetchEvent;
}

export interface Geo {
city?: string;
country?: {
code?: string;
name?: string;
};
subdivision?: {
code?: string;
name?: string;
};
}

export default function fastlyComputeAdapter(handler: HattipHandler) {
export default function fastlyComputeAdapter(
handler: HattipHandler<FastlyPlatformInfo>,
) {
addEventListener("fetch", (event) => {
const context: AdapterRequestContext<FastlyPlatformInfo> = {
request: event.request,
ip: event.client.address,
waitUntil: event.waitUntil.bind(event),
platform: {
name: "fastly-compute",
client: event.client,
event,
},
passThrough() {
// empty
Expand Down
11 changes: 10 additions & 1 deletion packages/adapter/adapter-netlify-edge/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,13 @@ export default netlifyEdgeAdapter(handler);

## `context.platform`

This adapter passes the [Netlify edge function context object](https://docs.netlify.com/netlify-labs/experimental-features/edge-functions/api/#netlify-specific-context-object) as `context.platform`. The type definitions are currently rudimentary and likely incomplete/inaccurate.
```ts
interface NetlifyEdgePlatformInfo {
/** Platform name */
name: "netlify-edge";
/** Netlify-specific context object */
context: NetlifyContext;
}
```

See [Netlify's documentation](https://docs.netlify.com/functions/build-with-javascript/#synchronous-function-format) for the Netlify-specific context object.
21 changes: 15 additions & 6 deletions packages/adapter/adapter-netlify-edge/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type { AdapterRequestContext, HattipHandler } from "@hattip/core";

export interface NetlifyEdgePlatformInfo {
interface NetlifyEdgePlatformInfo {
/** Platform name */
name: "netlify-edge";
/**
* Netlify-specific context object
* @see https://docs.netlify.com/edge-functions/api/#netlify-specific-context-object
*/
context: NetlifyContext;
}

export interface NetlifyContext {
ip: string | null;
cookies: Cookies;
geo: Geo;
Expand Down Expand Up @@ -70,18 +79,18 @@ export interface DeleteCookieOptions {

export type NetlifyEdgeFunction = (
request: Request,
info: Omit<NetlifyEdgePlatformInfo, "name">,
nlContext: NetlifyContext,
) => Response | undefined | Promise<Response | undefined>;

export default function netlifyEdgeAdapter(
handler: HattipHandler,
handler: HattipHandler<NetlifyEdgePlatformInfo>,
): NetlifyEdgeFunction {
return async function fetchHandler(request, info) {
return async function fetchHandler(request, nlContext) {
let passThroughCalled = false;

const context: AdapterRequestContext<NetlifyEdgePlatformInfo> = {
request,
ip: info.ip || "",
ip: nlContext.ip || "",
waitUntil() {
// No op
},
Expand All @@ -90,7 +99,7 @@ export default function netlifyEdgeAdapter(
},
platform: {
name: "netlify-edge",
...info,
context: nlContext,
},
env(variable) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
Expand Down
8 changes: 7 additions & 1 deletion packages/adapter/adapter-netlify-functions/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,10 @@ Calling `context.passThrough` has no effect, the placeholder response will be re

## `context.platform`

This adapter's platform context contains the `event` and `context` properties which have the types `NetlifyFunctionEvent` and `NetlifyFunctionContext` respectively.
```ts
export interface NetlifyFunctionsPlatformInfo {
name: "netlify-functions";
event: NetlifyFunctionEvent;
context: NetlifyFunctionContext;
}
```
2 changes: 1 addition & 1 deletion packages/adapter/adapter-netlify-functions/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface NetlifyFunctionsPlatformInfo {
export type { NetlifyFunctionEvent, NetlifyFunctionContext };

export default function netlifyFunctionsAdapter(
handler: HattipHandler,
handler: HattipHandler<NetlifyFunctionsPlatformInfo>,
): NetlifyFunction {
return async (event, netlifyContext) => {
const ip =
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter/adapter-node/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export interface NodePlatformInfo {
* middleware in Connect-style frameworks like Express.
*/
export function createMiddleware(
handler: HattipHandler,
handler: HattipHandler<NodePlatformInfo>,
options: NodeAdapterOptions = {},
): NodeMiddleware {
const {
Expand Down
12 changes: 6 additions & 6 deletions packages/adapter/adapter-test/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import installNodeFetch from "@hattip/polyfills/node-fetch";

installNodeFetch();

export interface CreateTestClientArgs {
handler: HattipHandler;
export interface CreateTestClientArgs<P = unknown> {
handler: HattipHandler<P>;
baseUrl?: string | URL;
platform?: any;
platform?: P;
env?: Record<string, string>;
}

export function createTestClient({
export function createTestClient<P = { name: "test" }>({
handler,
baseUrl,
platform = { name: "test" },
platform = { name: "test" } as any,
env = Object.create(null),
}: CreateTestClientArgs): typeof fetch {
}: CreateTestClientArgs<P>): typeof fetch {
return async function fetch(input, init) {
let request: Request;
if (input instanceof Request) {
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter/adapter-vercel-edge/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export type VercelEdgeFunction = (
) => Response | Promise<Response>;

export default function vercelEdgeAdapter(
handler: HattipHandler,
handler: HattipHandler<VercelEdgePlatformInfo>,
): VercelEdgeFunction {
return async function vercelEdgeFunction(request, event) {
let passThroughCalled = false;
Expand Down
32 changes: 21 additions & 11 deletions packages/base/compose/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export interface Locals {}
/**
* Request context
*/
export interface RequestContext
extends AdapterRequestContext,
export interface RequestContext<P = unknown>
extends AdapterRequestContext<P>,
RequestContextExtensions {
/** Parsed request URL */
url: URL;
Expand All @@ -35,23 +35,31 @@ export type MaybeRespone = ResponseLike | void;

export type MaybeAsyncResponse = MaybeRespone | Promise<MaybeRespone>;

export type RequestHandler = (context: RequestContext) => MaybeAsyncResponse;
export type RequestHandler<P = unknown> = (
context: RequestContext<P>,
) => MaybeAsyncResponse;

export type MaybeRequestHandler = false | null | undefined | RequestHandler;
export type MaybeRequestHandler<P = unknown> =
| false
| null
| undefined
| RequestHandler<P>;

export type RequestHandlerStack = MaybeRequestHandler | RequestHandlerStack[];
export type RequestHandlerStack<P = unknown> =
| MaybeRequestHandler<P>
| MaybeRequestHandler<P>[];

function finalHandler(context: RequestContext): Response {
context.passThrough();
return new Response("Not found", { status: 404 });
}

export type PartialHandler = (
context: RequestContext,
export type PartialHandler<P = unknown> = (
context: RequestContext<P>,
) => Response | void | Promise<Response | void>;

export function composePartial(
handlers: RequestHandlerStack[],
export function composePartial<P = unknown>(
handlers: RequestHandlerStack<P>[],
next?: () => Promise<Response>,
): PartialHandler {
const flatHandlers = handlers.flat().filter(Boolean) as RequestHandler[];
Expand All @@ -69,8 +77,10 @@ export function composePartial(
);
}

export function compose(...handlers: RequestHandlerStack[]): HattipHandler {
return composePartial([
export function compose<P = unknown>(
...handlers: RequestHandlerStack<P>[]
): HattipHandler<P> {
return composePartial<P>([
(context) => {
context.url = new URL(context.request.url);
context.method = context.request.method;
Expand Down
4 changes: 2 additions & 2 deletions packages/base/core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ export interface AdapterRequestContext<P = unknown> {
* @returns A response or a promise that resolves to a response.
* @see https://developer.mozilla.org/en-US/docs/Web/API/Response
*/
export type HattipHandler = (
context: AdapterRequestContext,
export type HattipHandler<P = unknown> = (
context: AdapterRequestContext<P>,
) => Response | Promise<Response>;

declare global {
Expand Down
Loading

0 comments on commit bec153b

Please sign in to comment.