Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make ctx.platform typed #80

Merged
merged 1 commit into from
Jul 20, 2023
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 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