Skip to content

Commit

Permalink
feat: add support for Bun
Browse files Browse the repository at this point in the history
  • Loading branch information
kitsonk committed Mar 1, 2024
1 parent 27cf442 commit c7d6ae3
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 11 deletions.
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
[![codecov](https://codecov.io/gh/oakserver/oak/branch/main/graph/badge.svg?token=KEKZ52NXGP)](https://codecov.io/gh/oakserver/oak)

A middleware framework for Deno's native HTTP server,
[Deno Deploy](https://deno.com/deploy) and Node.js 16.5 and later. It also
includes a middleware router.
[Deno Deploy](https://deno.com/deploy), Node.js 16.5 and later, and
[Bun](https://bun.sh/). It also includes a middleware router.

This middleware framework is inspired by [Koa](https://github.com/koajs/koa/)
and middleware router inspired by
Expand All @@ -34,6 +34,72 @@ resources.
> direct you at a particular version. So to use version 13.0.0 of oak, you would
> want to import `https://deno.land/x/oak@v13.0.0/mod.ts`.
## Usage

### Deno

oak is available on both [deno.land/x](https://deno.land/x/oak/) and [JSR](https://jsr.io/@oak/oak). To use from `deno.land/x`, import into a module:

```ts
import { Application } from "https://deno.land/x/oak/mod.ts";
```

To use from JSR, import into a module:

```ts
import { Application } from "jsr:@oak/oak@14";
```

### Node.js

oak is available for Node.js on both
[npm](https://www.npmjs.com/package/@oakserver/oak) and
[JSR](https://jsr.io/@oak/oak). To use from npm, install the package:

```
npm i @oakserver/oak@14
```

And then import into a module:

```js
import { Application } from "@oakserver/oak";
```

To use from JSR, install the package:

```
npx jsr i @oak/oak@14
```

And then import into a module:

```js
import { Application } from "@oak/oak";
```

> [!NOTE]
> Send, websocket upgrades and serving over TLS/HTTPS are not currently
> supported.
### Bun

oak is available for Bun on [JSR](https://jsr.io/@oak/oak). To use install the
package:

```
bunx jsr i @oak/oak@14
```

And then import into a module:

```ts
import { Application } from "@oak/oak";
```

> [!NOTE]
> Send and websocket upgrades are not currently supported.
## Application, middleware, and context

The `Application` class coordinates managing the HTTP server, running
Expand Down
15 changes: 11 additions & 4 deletions application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ import {
ServerConstructor,
ServerRequest,
} from "./types.ts";
import { createPromiseWithResolvers, isNetAddr, isNode } from "./util.ts";
import {
createPromiseWithResolvers,
isBun,
isNetAddr,
isNode,
} from "./util.ts";

/** Base interface for application listening options. */
export interface ListenOptionsBase {
Expand Down Expand Up @@ -134,7 +139,7 @@ interface ApplicationListenEventInit extends EventInit {
listener: Listener;
port: number;
secure: boolean;
serverType: "native" | "node" | "custom";
serverType: "native" | "node" | "bun" | "custom";
}

type ApplicationListenEventListenerOrEventListenerObject =
Expand Down Expand Up @@ -312,7 +317,7 @@ export class ApplicationListenEvent extends Event {
listener: Listener;
port: number;
secure: boolean;
serverType: "native" | "node" | "custom";
serverType: "native" | "node" | "bun" | "custom";

constructor(eventInitDict: ApplicationListenEventInit) {
super("listen", eventInitDict);
Expand Down Expand Up @@ -658,7 +663,9 @@ export class Application<AS extends State = Record<string, any>>
options = Object.assign({ port: 0 }, options);
if (!this.#serverConstructor) {
if (!DefaultServerCtor) {
const { Server } = await (isNode()
const { Server } = await (isBun()
? import("./http_server_bun.ts")
: isNode()
? import("./http_server_node.ts")
: import("./http_server_native.ts"));
DefaultServerCtor = Server as ServerConstructor<ServerRequest>;
Expand Down
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "@oak/oak",
"version": "14.0.0",
"version": "14.1.0-alpha.2",
"exports": {
".": "./mod.ts",
"./application": "./application.ts",
"./helpers": "./helpers.ts",
"./etag": "./etag.ts",
"./form_data": "./form_data.ts",
"./http_server_bun": "./http_server_bun.ts",
"./http_server_native": "./http_server_native.ts",
"./http_server_node": "./http_server_node.ts",
"./proxy": "./middleware/proxy.ts",
Expand Down
122 changes: 122 additions & 0 deletions http_server_bun.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright 2018-2024 the oak authors. All rights reserved. MIT license.

// deno-lint-ignore-file no-explicit-any

import { assert } from "./deps.ts";
import { assertEquals } from "./test_deps.ts";
import { createMockApp } from "./testing.ts";

import { Server } from "./http_server_bun.ts";

interface SocketAddress {
address: string;
port: number;
family: "IPv4" | "IPv6";
}

let currentServer: MockBunServer | undefined;
let requests: Request[] = [];

class MockBunServer {
stoppedCount = 0;
fetch: (
req: Request,
server: this,
) => Response | Promise<Response>;
responses: Response[] = [];

development: boolean;
hostname: string;
port: number;
pendingRequests = 0;

async #run() {
for (const req of requests) {
const res = await this.fetch(req, this);
this.responses.push(res);
}
}

constructor(
{ fetch, hostname, port, development }: {
fetch: (
req: Request,
server: unknown,
) => Response | Promise<Response>;
hostname?: string;
port?: number;
development?: boolean;
error?: (error: Error) => Response | Promise<Response>;
tls?: {
key?: string;
cert?: string;
};
},
) {
this.fetch = fetch;
this.development = development ?? false;
this.hostname = hostname ?? "localhost";
this.port = port ?? 567890;
currentServer = this;
this.#run();
}

requestIP(_req: Request): SocketAddress | null {
return { address: "127.0.0.0", port: 567890, family: "IPv4" };
}

stop(): void {
this.stoppedCount++;
}
}

function setup(reqs?: Request[]) {
if (reqs) {
requests = reqs;
}
(globalThis as any)["Bun"] = {
serve(options: any) {
return new MockBunServer(options);
},
};
}

function teardown() {
delete (globalThis as any)["Bun"];
currentServer = undefined;
}

Deno.test({
name: "bun server can listen",
async fn() {
setup();
const server = new Server(createMockApp(), { port: 8080 });
const listener = await server.listen();
assertEquals(listener, { addr: { hostname: "localhost", port: 8080 } });
assert(currentServer);
assertEquals(currentServer.stoppedCount, 0);
server.close();
assertEquals(currentServer.stoppedCount, 1);
teardown();
},
});

Deno.test({
name: "bun server can process requests",
async fn() {
setup([new Request(new URL("http://localhost:8080/"))]);
const server = new Server(createMockApp(), { port: 8080 });
const listener = await server.listen();
assertEquals(listener, { addr: { hostname: "localhost", port: 8080 } });
assert(currentServer);
for await (const req of server) {
assert(!req.body);
assertEquals(req.url, "/");
await req.respond(new Response("hello world"));
}
server.close();
assertEquals(currentServer.stoppedCount, 1);
assertEquals(currentServer.responses.length, 1);
teardown();
},
});
Loading

0 comments on commit c7d6ae3

Please sign in to comment.