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
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions jsr.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"./schema": "./src/schema.ts",
"./adapters/zod-schema-adapter": "./src/adapters/zod-schema-adapter.ts",
"./transports/bun-transport": "./src/transports/bun-transport.ts",
"./transports/fetch-transport": "./src/transports/fetch-transport.ts",
"./transports/deno-transport": "./src/transports/deno-transport.ts"
}
}
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
"types": "./dist/transports/bun-transport.d.mts",
"default": "./dist/transports/bun-transport.mjs"
},
"./transports/fetch-transport": {
"types": "./dist/transports/fetch-transport.d.mts",
"default": "./dist/transports/fetch-transport.mjs"
},
"./transports/deno-transport": {
"types": "./dist/transports/deno-transport.d.mts",
"default": "./dist/transports/deno-transport.mjs"
Expand All @@ -58,7 +62,7 @@
],
"scripts": {
"dev": "bun test --watch",
"build": "tsdown src/{index,types,client,testing,schema,adapters/zod-schema-adapter,transports/bun-transport,transports/deno-transport}.ts",
"build": "tsdown src/{index,types,client,testing,schema,adapters/zod-schema-adapter,transports/bun-transport,transports/fetch-transport,transports/deno-transport}.ts",
"bench": "bun run src/__tests__/bench.ts",
"example": "bun --watch run example.ts",
"example:prod": "NODE_ENV=production bun run example",
Expand All @@ -80,6 +84,7 @@
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"@types/bun": "latest",
"@types/deno": "^2.5.0",
"@typescript/native-preview": "^7.0.0-dev.20260202.1",
"changelogen": "^0.6.2",
"cookie": "^1.1.1",
Expand All @@ -99,11 +104,15 @@
"zod": "^4.1.11"
},
"peerDependencies": {
"@types/bun": "*"
"@types/bun": "*",
"@types/deno": "*"
},
"peerDependenciesMeta": {
"@types/bun": {
"optional": true
},
"@types/deno": {
"optional": true
}
}
}
22 changes: 21 additions & 1 deletion src/__tests__/app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { zodSchemaAdapter } from "../adapters/zod-schema-adapter";
import { createApp } from "../app";
import { HttpStatus } from "../status";
import { createTestAppClient } from "../testing";
import type { AnyDef, GetAppData } from "../types";
import type { AnyDef, GetAppData, Transport } from "../types";
import { createBunTransport } from "../transports/bun-transport";

// Silence console.error logs
globalThis.console.error = mock();
Expand Down Expand Up @@ -466,6 +467,7 @@ describe("App", () => {
"/": AnyDef;
};
};
transport: Transport;
}>();
expect(actual).not.toMatchObject({ a: "A" });
});
Expand All @@ -489,6 +491,7 @@ describe("App", () => {
"/": AnyDef;
};
};
transport: Transport;
}>();
expect(actual).toMatchObject({ a: "A" });
});
Expand Down Expand Up @@ -608,4 +611,21 @@ describe("App", () => {
});
});
});

describe("transports", () => {
it("should include transport decorations", async () => {
const expected = Symbol("server") as any;

let actual: Bun.Server;
const app = createApp({
schemaAdapter: zodSchemaAdapter,
transport: createBunTransport(),
}).get("/", (ctx) => {
actual = ctx.server;
});
await app.build()(new Request("http://localhost:3000"), expected);

expect(actual!).toBe(expected);
});
});
});
3 changes: 2 additions & 1 deletion src/__tests__/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect, mock } from "bun:test";
import { createApp } from "../app";
import { createTestAppClient } from "../testing";
import type { App } from "../types";
import type { App, Transport } from "../types";
import type { GetClientRoutes } from "../client";
import { expectTypeOf } from "expect-type";
import { z } from "zod/v4";
Expand Down Expand Up @@ -154,6 +154,7 @@ describe("Client", () => {
"/images": {};
};
};
transport: Transport;
}>;
type Expected = {
GET: {
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { z } from "zod/v4";
import type { StandardSchemaV1 } from "@standard-schema/spec";
import { ErrorResponse } from "../schema";
import type { HttpStatus } from "../status";
import type { BunTransport } from "../transports/bun-transport";

describe("Types", () => {
describe("MergeRoutes", () => {
Expand Down Expand Up @@ -86,6 +87,7 @@ describe("Types", () => {
routes: {};
exported: false;
ctx: { a: "A" };
transport: t.Transport;
};
type B = {
routes: { GET: { "/b": {} } };
Expand All @@ -95,6 +97,7 @@ describe("Types", () => {
exported: A["exported"];
ctx: { a: "A" };
routes: { GET: { "/b": {} } };
transport: t.Transport;
};

type Actual = t.MergeAppData<A, B>;
Expand All @@ -108,18 +111,21 @@ describe("Types", () => {
exported: false;
ctx: { a: "A" };
routes: { GET: { "/a": {} } };
transport: t.Transport;
};
type B = {
prefix: "/test";
exported: true;
ctx: { b: "B" };
routes: { GET: { "/b": {} } };
transport: BunTransport;
};
type Expected = {
prefix: "/test";
exported: true;
ctx: { a: "A"; b: "B" };
routes: { GET: { "/a": {}; "/b": {} } };
transport: t.Transport;
};

type Actual = t.MergeAppData<A, B>;
Expand Down Expand Up @@ -389,6 +395,7 @@ describe("Types", () => {
[p in Path]: Def;
};
};
transport: t.Transport;
};
type Expected = {
params: Record<string, string>;
Expand All @@ -414,6 +421,7 @@ describe("Types", () => {
[p in Path]: Def;
};
};
transport: t.Transport;
};
type Expected = Ctx;

Expand All @@ -434,6 +442,7 @@ describe("Types", () => {
[p in Path]: Def;
};
};
transport: t.Transport;
};
type Expected = {
route: Path;
Expand Down Expand Up @@ -464,6 +473,7 @@ describe("Types", () => {
[p in Path]: Def;
};
};
transport: t.Transport;
};
type Expected = {
route: Path;
Expand All @@ -487,6 +497,7 @@ describe("Types", () => {
routes: {
GET: { "/test": { body: z.ZodObject<{ id: z.ZodString }> } };
};
transport: t.Transport;
}>;
type Expected = MyApp;

Expand All @@ -503,6 +514,7 @@ describe("Types", () => {
routes: {
GET: { "/test": { body: z.ZodObject<{ id: z.ZodString }> } };
};
transport: t.Transport;
}>;
type Expected = t.App<{
prefix: "";
Expand All @@ -511,6 +523,7 @@ describe("Types", () => {
routes: {
GET: { "/api/test": { body: z.ZodObject<{ id: z.ZodString }> } };
};
transport: t.Transport;
}>;

type Actual = t.ApplyAppPrefix<MyApp>;
Expand Down Expand Up @@ -541,6 +554,7 @@ describe("Types", () => {
};
};
};
transport: t.Transport;
};
type Expected = {
ctx: AppData["ctx"];
Expand All @@ -555,6 +569,7 @@ describe("Types", () => {
"/api/users": AppData["routes"]["POST"]["/users"];
};
};
transport: t.Transport;
};

type Actual = t.ApplyAppDataPrefix<AppData>;
Expand All @@ -570,6 +585,7 @@ describe("Types", () => {
exported: false;
prefix: "/api";
routes: {};
transport: t.Transport;
};
type ChildAppData = {
ctx: { b: "b" };
Expand All @@ -578,12 +594,14 @@ describe("Types", () => {
routes: {
GET: { "/users": t.AnyDef };
};
transport: BunTransport;
};
type Expected = {
ctx: { a: "a" };
exported: false;
prefix: "/api";
routes: ChildAppData["routes"];
transport: t.Transport;
};

type Actual = t.UseAppData<ParentAppData, ChildAppData>;
Expand All @@ -597,6 +615,7 @@ describe("Types", () => {
exported: false;
prefix: "/api";
routes: {};
transport: t.Transport;
};
type ChildAppData = {
ctx: { b: "b" };
Expand All @@ -605,6 +624,7 @@ describe("Types", () => {
routes: {
GET: { "/users": t.AnyDef };
};
transport: BunTransport;
};
type Expected = {
ctx: { a: "a"; b: "b" };
Expand All @@ -613,6 +633,7 @@ describe("Types", () => {
routes: {
GET: { "/users": t.AnyDef };
};
transport: t.Transport;
};

type Actual = t.UseAppData<ParentAppData, ChildAppData>;
Expand Down
29 changes: 21 additions & 8 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { addRoute, createRouter } from "rou3";
import { compileRouter } from "rou3/compiler";
import { compileFetchFunction } from "./internal/compile-fetch-function";
import { compileRouteHandler } from "./internal/compile-route-handler";
import { detectTransport } from "./internal/utils";
import { buildOpenApiDocs, buildScalarHtml } from "./open-api";
import type {
AnyTransport,
App,
BasePath,
BasePrefix,
Expand All @@ -18,6 +18,7 @@ import type {
ServerSideFetch,
Transport,
} from "./types";
import { createFetchTransport } from "./transports/fetch-transport";

let appIdInc = 0;
const nextAppId = () => `app-${appIdInc++}`;
Expand Down Expand Up @@ -47,25 +48,33 @@ const nextHookId = (appId: string) => `${appId}/hook-${_hookIdInc++}`;
* // Or serve the app yourself
* const fetch = app.build();
* Bun.serve({ fetch, ... });
* Deno.serve({ fetch, ... });
* Deno.serve({ ... }, fetch);
* ```
*
* @param options Configure application behavior.
*/
export function createApp<TPrefix extends BasePrefix = "">(
options?: CreateAppOptions<TPrefix>,
export function createApp<
TPrefix extends BasePrefix = "",
TTransport extends AnyTransport = Transport,
>(
options?: CreateAppOptions<TPrefix, TTransport>,
): App<{
ctx: {};
exported: false;
prefix: TPrefix;
routes: {};
transport: TTransport;
}> {
const appId = nextAppId();

const { origin = "http://localhost", prefix = "" } = options ?? {};
const hooks: App["~zeta"]["hooks"] = {};
const routes: App["~zeta"]["routes"] = {};

let _transport: AnyTransport;
const getTransport = (): AnyTransport =>
(_transport ??= options?.transport ?? createFetchTransport());

const addRoutesEntry = (method: string, route: string, data: RouterData) => {
routes[method] ??= {};
if (routes[method][route]) {
Expand Down Expand Up @@ -132,7 +141,8 @@ export function createApp<TPrefix extends BasePrefix = "">(
}

const getRoute = compileRouter(router);
return compileFetchFunction({ getRoute, hooks, origin });
const transport = getTransport();
return compileFetchFunction({ getRoute, hooks, origin, transport });
},

getOpenApiSpec: () => {
Expand All @@ -148,7 +158,7 @@ export function createApp<TPrefix extends BasePrefix = "">(
},

listen: (port, cb) => {
const transport = options?.transport ?? detectTransport();
const transport = getTransport();
transport.listen(port, app.build(), cb);
return app;
},
Expand Down Expand Up @@ -352,7 +362,10 @@ export function createApp<TPrefix extends BasePrefix = "">(
/**
* Configure how the app is created.
*/
export type CreateAppOptions<TPrefix extends BasePrefix = ""> = {
export type CreateAppOptions<
TPrefix extends BasePrefix = "",
TTransport extends AnyTransport = Transport,
> = {
/**
* The origin to use when constructing URLs.
* @default "http://localhost"
Expand Down Expand Up @@ -397,7 +410,7 @@ export type CreateAppOptions<TPrefix extends BasePrefix = ""> = {
* });
* ```
*/
transport?: Transport;
transport?: TTransport;

/**
* Where the OpenAPI JSON docs is hosted.
Expand Down
Loading
Loading