Skip to content
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ dist
# test directory
test-app

# example app artifacts
examples/*/node_modules
examples/*/.svelte-kit
examples/*/build

# code coverage
coverage
*.lcov
Expand Down
6 changes: 3 additions & 3 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ The `export const websocket` (`Bun.WebSocketHandler`) from `hooks.server`. Adapt
_Avoid_: handleWebsocket, adapter websocket option

**Kit platform**:
`event.platform` is `{ server, request }` so `handle` can call `event.platform.server.upgrade(event.platform.request)`.
_Avoid_: isBun, upgrade callback
`event.platform` is `{ server, request }` so `handle` can call `event.platform.server.upgrade(event.platform.request)`. Apps pick this up with `/// <reference types="@gkh/svelte-adapter-bun" />` in `src/app.d.ts`, which also pulls `bun-types` so `Bun.redis` and other Bun globals typecheck. Do not ask apps to copy `App.Platform` by hand.
_Avoid_: isBun, upgrade callback, documenting a handwritten Platform block as the setup step

**Client address**:
What `getClientAddress` returns for SvelteKit. If `ADDRESS_HEADER` is set, read that header (for `x-forwarded-for`, from the right by `XFF_DEPTH`). If it is unset, use `Bun.Server.requestIP`. Do not rewrite the request’s `X-Forwarded-For`.
Expand All @@ -54,7 +54,7 @@ _Avoid_: sirv, serveAssets
_Avoid_: process idle shutdown (adapter-node’s meaning of IDLE_TIMEOUT)

**Request origin**:
The public URL origin for `event.url`. `ORIGIN` wins. Otherwise `PROTOCOL_HEADER` (default `https` — TLS-terminating proxies) + `HOST_HEADER` or `Host` + optional `PORT_HEADER` (only when the host has no port).
The public URL origin for `event.url`. `ORIGIN` wins. Otherwise forwarded proto from `PROTOCOL_HEADER` (when present) + `HOST_HEADER` or `Host` + optional `PORT_HEADER` (only when the host has no port). When forwarded proto is unset or not configured, use the incoming request URL protocol (`http` vs `https`).
_Avoid_: request.url as-is, default http

**Kit read**:
Expand Down
48 changes: 39 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ A high-performance SvelteKit adapter that leverages Bun's native APIs for optima
bun add -D svelte-adapter-bun
```

## Example app

`examples/kitchen-sink` is a SvelteKit app that exercises every adapter contract
(deploy output, listen/origin env, client address, assets, ranges, WebSocket,
`$app/server` `read`, instrumentation, and shutdown). `example.test.ts` builds
that app and hits the running Bun server.

```bash
bun run build
cd examples/kitchen-sink
bun install
bun run build
# adapt() uses Bun.build / Bun.file — the example script is `bun --bun vite build`
ORIGIN=http://127.0.0.1:3000 bun ./build/index.js
```

## Usage

In your `svelte.config.js`:
Expand Down Expand Up @@ -91,21 +107,34 @@ export const websocket: Bun.WebSocketHandler = {
};
```

`event.platform` is `{ server, request }`. Declare it on `App.Platform` in `src/app.d.ts`:
`event.platform` is `{ server, request }`. Do not copy that interface by hand.
Reference the adapter from `src/app.d.ts` so TypeScript loads `bun-types` and
augments `App.Platform` in one step:

```ts
/// <reference types="@gkh/svelte-adapter-bun" />

declare global {
namespace App {
interface Platform {
server: Bun.Server;
request: Request;
}
// Platform is { server: Bun.Server; request: Request } from the adapter.
// Add your own fields here if you put extra data on event.platform.
}
}

export {};
```

That reference is how you get **Bun globals** as well (`Bun.redis`, `Bun.file`,
`Bun.sql`, `Bun.s3`, …). Those live on the `Bun` namespace from `bun-types`,
not on `event.platform`. The adapter does not wrap them — they are process-wide
once the app is running on Bun. `App.Platform` is only the per-request object
passed into `server.respond` so `handle` can call
`event.platform.server.upgrade(event.platform.request)`.

Install `bun-types` next to the adapter (`bun add -d bun-types`). Without the
reference (or a `/// <reference types="bun-types" />` of your own), `Bun` is an
unknown name and `event.platform` stays Kit’s empty `Platform`.

Connect from the client:

```js
Expand Down Expand Up @@ -138,7 +167,7 @@ With no prefix, `PORT` is omitted from `Bun.serve` so Bun 1.4 can read `PORT`, `
ADDRESS_HEADER=X-Forwarded-For XFF_DEPTH=2 bun ./build/index.js
```

The public origin for `event.url` is `ORIGIN` if set. Otherwise it is `PROTOCOL_HEADER` (default `https` — typical behind a TLS-terminating proxy) + `HOST_HEADER` or the request `Host` + optional `PORT_HEADER` (appended only when the host has no port). If `PROTOCOL_HEADER` is unset, the protocol is `https` so CSRF checks match the browser origin when Bun.serve only saw `http`.
The public origin for `event.url` is `ORIGIN` if set. Otherwise it is the forwarded protocol from `PROTOCOL_HEADER` (when that header is present on the request) + `HOST_HEADER` or the request `Host` + optional `PORT_HEADER` (appended only when the host has no port). When `PROTOCOL_HEADER` is configured but missing on a request, or when no forwarded headers are configured at all, the protocol falls back to the incoming request URL (`http` for plain `Bun.serve`, `https` when the request arrived as HTTPS) so same-origin form actions and CSRF checks match what the browser sent.

`read` from `$app/server` works. The adapter claims `supports.read` and `Server.init` streams files from `client/` (plus Kit `paths.base`) via `Bun.file`.

Expand All @@ -156,10 +185,11 @@ BODY_SIZE_LIMIT=2M IDLE_TIMEOUT=30 bun ./build/index.js

## Building and Running

After building your app:
After building your app with Bun (`bun --bun vite build` or `bun run build`
if the script already uses Bun):

```bash
bun run build
bun --bun vite build
```

The adapter generates a minimal `package.json` in the build directory. To run the server:
Expand Down Expand Up @@ -242,7 +272,7 @@ Intentional differences:
| Assets | `assets` (not `serveAssets`). `Bun.file` plus `.br` / `.gz` negotiation, not sirv. Single and multipart `Range: bytes=` via `Bun.file.slice` | Avoid a Node static-server dependency; precompress already wrote the siblings |
| `BODY_SIZE_LIMIT` | `Infinity` / `0` / `none` disable the cap | adapter-node’s documented off switch; gornostay rejects `Infinity` at boot |
| `IDLE_TIMEOUT` | Must be `0`–`255` (Bun per-connection idle). Out-of-range values throw a clear error | Bun will crash on `256+`. This is not adapter-node’s process idle-shutdown |
| Request origin | `ORIGIN`, or forwarded headers with protocol default `https`. Empty `HOST_HEADER` means use `Host`. `PORT_HEADER` is not appended when the host already has a port | Same shape as gornostay. Default `https` keeps CSRF working behind TLS-terminating proxies (`request.url` on Bun.serve is usually `http`) |
| Request origin | `ORIGIN`, or forwarded headers; when proto is unset, fall back to the request URL protocol. Empty `HOST_HEADER` means use `Host`. `PORT_HEADER` is not appended when the host already has a port | Same shape as gornostay. Request-protocol fallback keeps local `http://` CSRF honest; set `PROTOCOL_HEADER` (or `ORIGIN`) behind TLS-terminating proxies |
| `development` / `dynamic_origin` | Not accepted | Leftover from old gornostay. Current gornostay dropped them. Origin is always `ORIGIN` or forwarded headers; we do not minify the Bun entry |

## License
Expand Down
20 changes: 20 additions & 0 deletions adapter-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, test } from "bun:test";

describe("adapter ambient types", () => {
test("references bun-types so Bun.redis and other globals are in scope", async () => {
const ambient = await Bun.file(new URL("./ambient.d.ts", import.meta.url)).text();
expect(ambient).toMatch(/\/\/\/\s*<reference types="bun-types"\s*\/>/);
expect(ambient).toContain("interface Platform");
expect(ambient).toContain("server: Bun.Server");
expect(ambient).toContain("request: Request");
});

test("README tells apps to reference the adapter instead of copying Platform", async () => {
const readme = await Bun.file(new URL("./README.md", import.meta.url)).text();
expect(readme).toContain('/// <reference types="@gkh/svelte-adapter-bun" />');
expect(readme).toContain("Bun.redis");
expect(readme).not.toMatch(
/Declare it on `App\.Platform` in `src\/app\.d\.ts`:\s*```ts\s*declare global/,
);
});
});
20 changes: 20 additions & 0 deletions ambient.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// <reference types="bun-types" />

/**
* Pulled into the app by:
*
* ```ts
* /// <reference types="@gkh/svelte-adapter-bun" />
* ```
*
* Bun globals (`Bun.redis`, `Bun.file`, `Bun.sql`, …) come from `bun-types`.
* `App.Platform` is the per-request object the adapter passes to `server.respond`.
*/
declare global {
namespace App {
interface Platform {
server: Bun.Server;
request: Request;
}
}
}
9 changes: 5 additions & 4 deletions build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const distPkg = {
"types": "./websocket.d.ts"
}
},
files: ["index.js", "index.js.map", "websocket.js", "websocket.js.map", "*.d.ts", "files"],
files: ["index.js", "index.js.map", "websocket.js", "websocket.js.map", "*.d.ts", "ambient.d.ts", "files"],
keywords: pkg.keywords,
author: pkg.author,
license: pkg.license,
Expand Down Expand Up @@ -115,10 +115,11 @@ if (existsSync("src")) {
console.log("📁 Copied runtime files to dist/files");
}

// Generate TypeScript declarations
try {
await Bun.$`bunx tsc --declaration --emitDeclarationOnly --outDir dist`;
console.log("📝 Generated TypeScript declarations");
await Bun.$`bunx tsc --noEmit`;
await Bun.write("dist/ambient.d.ts", await Bun.file("ambient.d.ts").text());
await Bun.write("dist/index.d.ts", await Bun.file("index.d.ts").text());
console.log("📝 Published ambient and adapter types");
} catch (error) {
console.error("❌ Failed to generate TypeScript declarations:", error);
process.exit(1);
Expand Down
Loading