Skip to content

Commit

Permalink
Make runtime api stable (#77)
Browse files Browse the repository at this point in the history
feat: document runtime and add adapter specific properties
  • Loading branch information
magne4000 authored Dec 4, 2024
1 parent 1d1703b commit 203febf
Show file tree
Hide file tree
Showing 30 changed files with 366 additions and 62 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ export default defineConfig({
text: "Using env variables",
link: "/recipes/env-helper",
},
{
text: "Using runtime information",
link: "/recipes/runtime",
},
],
},
{
Expand Down
12 changes: 8 additions & 4 deletions docs/definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ A function that returns a [Response](https://developer.mozilla.org/en-US/docs/We
It will usually be associated with a _route_.

```ts twoslash
import type { RuntimeAdapter } from "@universal-middleware/core";
// ---cut---
interface UniversalHandler<Context> {
(request: Request, context: Context): Response | Promise<Response>;
(request: Request, context: Context, runtime: RuntimeAdapter):
Response | Promise<Response>;
}
```

Expand All @@ -24,9 +27,10 @@ Check the [recipes](/recipes/context-middleware) for details.

```ts twoslash
type Awaitable<T> = T | Promise<T>;

interface UniversalMiddleware<InContext, OutContext> { // [!code focus:9]
(request: Request, context: InContext):
import type { RuntimeAdapter } from "@universal-middleware/core";
// ---cut---
interface UniversalMiddleware<InContext, OutContext> {
(request: Request, context: InContext, runtime: RuntimeAdapter):
Awaitable<
| Response // Can return an early Response
| OutContext // Can return a new context. Ensures type-safe context representation
Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/context-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
In this example, we're creating a middleware that adds a `hello` property to the [context](/definitions#context).
This property will then be accessible to any subsequent middleware or handler.

<<< @/../examples/tool/src/middlewares/context.middleware.ts
<<< @/../examples/tool/src/middlewares/context.middleware.ts {ts twoslash}

After bundling this middleware, one can then use this middleware as follows:

Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/env-helper.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The `env()` function helps to retrieve environment variables across all supported runtimes.

```ts
```ts twoslash
import { env, type Get, type UniversalMiddleware } from "@universal-middleware/core";

const handler = (() => (request, context, runtime) => {
Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/guard-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The following middleware is in charge of returning an early Response in case of unauthenticated user access.

<<< @/../examples/tool/src/middlewares/guard.middleware.ts
<<< @/../examples/tool/src/middlewares/guard.middleware.ts {ts twoslash}

After bundling this middleware, one can then use this middleware as follows:

Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/headers-middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The following middleware demonstrate how to interact with or modify a Response.
> Contrary to some middleware patterns out there, universal middlewares do not use a `next()` function.
> Instead, a middleware can return a function that will take the response as its first parameter.
<<< @/../examples/tool/src/middlewares/headers.middleware.ts
<<< @/../examples/tool/src/middlewares/headers.middleware.ts {ts twoslash}

After bundling this middleware, one can then use this middleware as follows:

Expand Down
2 changes: 1 addition & 1 deletion docs/recipes/params-handler.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Most adapters natively support route parameters (also called _parametric path_ o

We recommend to follow this next example when using route parameters:

<<< @/../examples/tool/src/handlers/params.handler.ts
<<< @/../examples/tool/src/handlers/params.handler.ts {ts twoslash}

> [!NOTE]
> For servers supporting route parameters (`app.get("/user/:name", myHandler())`), the parameters are available under `runtime.params`.
Expand Down
174 changes: 174 additions & 0 deletions docs/recipes/runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Using runtime information

The runtime data and original adapter parameters can be accessed through the third `runtime` parameter
in any middleware or handler. For instance:

```ts twoslash
import { env, type Get, type UniversalMiddleware } from "@universal-middleware/core";

const handler = (() => (request, context, runtime) => {
if (runtime.adapter === "express") {
runtime.req;
// ^?
}
}) satisfies Get<[], UniversalMiddleware>;
```

### Available properties

::: code-group

```ts twoslash [hono]
// @noErrors
import type { HonoAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<HonoAdapter>;
// ^^^^^^^
```

```ts twoslash [h3]
// @noErrors
import type { H3Adapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<H3Adapter>;
// ^^^^^^^
```

```ts twoslash [hattip]
// @noErrors
import type { HattipAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<HattipAdapter>;
// ^^^^^^^
```

```ts twoslash [cloudflare-worker]
// @noErrors
import type { CloudflareWorkerAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<CloudflareWorkerAdapter>;
// ^^^^^^^
```

```ts twoslash [cloudflare-pages]
// @noErrors
import type { CloudflarePagesAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<CloudflarePagesAdapter>;
// ^^^^^^^
```

```ts twoslash [express]
// @noErrors
import type { ExpressAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<ExpressAdapter>;
// ^^^^^^^
```

```ts twoslash [fastify]
// @noErrors
import type { FastifyAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<FastifyAdapter>;
// ^^^^^^^
```

```ts twoslash [elysia]
// @noErrors
import type { ElysiaAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<ElysiaAdapter>;
// ^^^^^^^
```

```ts twoslash [vercel-edge]
// @noErrors
import type { VercelEdgeAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<VercelEdgeAdapter>;
// ^^^^^^^
```

```ts twoslash [vercel-node]
// @noErrors
import type { VercelNodeAdapter } from "@universal-middleware/core";

export type Explain<A extends any> =
A extends Function
? A
: {[K in keyof A]: A[K]} & unknown

// ---cut---
// hover me
type Runtime = Explain<VercelNodeAdapter>;
// ^^^^^^^
```

:::

> [!NOTE]
> All runtime types are defined in [types.ts](https://github.com/magne4000/universal-middleware/blob/main/packages/core/src/types.ts)
2 changes: 1 addition & 1 deletion examples/tool/src/middlewares/context.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import type { Get, UniversalMiddleware } from "@universal-middleware/core";

const contextMiddleware = ((value) => (request, ctx) => {
const contextMiddleware = ((value) => (request, ctx, runtime) => {
// Return the new universal context, thus keeping complete type safety
// A less typesafe way to do the same thing would be to `ctx.something = value` and return nothing
return {
Expand Down
2 changes: 1 addition & 1 deletion examples/tool/src/middlewares/guard.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface User {
}

// This middleware will return an early Response for unauthenticated users
const guardMiddleware = (() => (request, ctx) => {
const guardMiddleware = (() => (request, ctx, runtime) => {
if (!ctx?.user) {
return new Response("Unauthorized", {
status: 401,
Expand Down
2 changes: 1 addition & 1 deletion examples/tool/src/middlewares/headers.middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Get, UniversalMiddleware } from "@universal-middleware/core";

// This middleware will add a `X-Universal-Hello` header to all responses
const headersMiddleware = (() => (request, ctx) => {
const headersMiddleware = (() => (request, ctx, runtime) => {
return (response) => {
// `ctx.hello` exists if it has been set by another middleware
response.headers.set("X-Universal-Hello", ctx.hello ?? "world");
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@universal-middleware/core": "workspace:^"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20241127.0",
"@cloudflare/workers-types": "catalog:",
"@swc/core": "catalog:",
"@types/node": "catalog:",
"@universal-middleware/tests": "workspace:*",
Expand Down
3 changes: 3 additions & 0 deletions packages/adapter-cloudflare/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,13 @@ export function getRuntime(
): RuntimeAdapter {
const isContext = args.length === 1;

const key = isContext ? "cloudflare-pages" : "cloudflare-worker";

return getAdapterRuntime(
isContext ? "cloudflare-pages" : "cloudflare-worker",
{
params: isContext ? ((args[0].params as Record<string, string>) ?? undefined) : undefined,
[key]: isContext ? args[0] : { env: args[0], ctx: args[1] },
},
{
env: isContext ? args[0].env : args[0],
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-elysia/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@universal-middleware/core": "workspace:^"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20241127.0",
"@cloudflare/workers-types": "catalog:",
"@swc/core": "catalog:",
"@types/node": "catalog:",
"@universal-middleware/tests": "workspace:*",
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-elysia/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function getRuntime(elysiaContext: ElysiaContext): RuntimeAdapter {
"elysia",
{
params,
elysia: elysiaContext,
},
cloudflareContext,
);
Expand Down
4 changes: 4 additions & 0 deletions packages/adapter-express/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,9 @@ export function getRuntime(request: DecoratedRequest, response: DecoratedServerR
params: request.params,
req: request as IncomingMessage,
res: response,
express: Object.freeze({
req: request as IncomingMessage,
res: response,
}),
});
}
4 changes: 4 additions & 0 deletions packages/adapter-fastify/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,5 +255,9 @@ export function getRuntime(request: FastifyRequest, reply: FastifyReply): Runtim
params: request.params as Record<string, string> | undefined,
req: request.raw as IncomingMessage,
res: reply.raw,
fastify: Object.freeze({
request: request,
reply: reply,
}),
});
}
1 change: 1 addition & 0 deletions packages/adapter-h3/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ export function getRuntime(event: H3Event): RuntimeAdapter {
"h3",
{
params: event.context.params,
h3: event,
},
{
req: event.node.req,
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-hattip/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@hattip/adapter-deno": "^0.0.49",
"@hattip/adapter-node": "^0.0.49",
"@hattip/compose": "^0.0.49",
"@hattip/core": "^0.0.49",
"@hattip/core": "catalog:",
"@hattip/cors": "^0.0.49",
"@hattip/router": "catalog:",
"@swc/core": "catalog:",
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-hattip/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function getRuntime(context: AdapterRequestContext): RuntimeAdapter {
"hattip",
{
params: "params" in context ? (context.params as Record<string, string>) : undefined,
hattip: context,
},
{
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
Expand Down
1 change: 1 addition & 0 deletions packages/adapter-hono/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export function getRuntime(honoContext: HonoContext): RuntimeAdapter {
"hono",
{
params,
hono: honoContext,
},
{
env: honoContext.env,
Expand Down
Loading

0 comments on commit 203febf

Please sign in to comment.