A project made with bun init already has Bun’s types and a
tsconfig.json that works. For an existing project, add Bun’s types:
Terminal window
bunadd-d@types/bun
and check tsconfig.json against this one:
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleResolution": "bundler",
"types": ["bun"],
"strict": true,
"noEmit": true,
"skipLibCheck": true
}
}
strict is required. Without strictNullChecks the types cannot tell a
field that exists from one that might not.
The old node resolution (also called node10) does not read package
exports and reports @tetsujs/core as not found.
Stricter flags such as noUncheckedIndexedAccess and
exactOptionalPropertyTypes work: the packages are checked under
@tsconfig/strictest.
Bun’s types also describe what Bun adds to the platform’s globals. Their
fetch has preconnect, so typeof fetch requires it too, and a
stand-in typed with it, such as a fake in a test, fails to compile with
“Property ‘preconnect’ is missing”. Type a dependency on fetch by its
call instead. fetch itself still fits:
typeFetch= (
input: string |URL| Request
input:string|URL|Request,
init: RequestInit |undefined
init?:RequestInit) =>Promise<Response>;
constweatherController=controller("Weather", ({
fetch: Fetch
fetch }: {
fetch: Fetch
fetch:Fetch }) => ({
today: route({
method: "GET"
The method this route answers.
Kept as a literal rather than widened to
Method
: the method is
half of a route's identity, and an application that remembers its
routes — for a generated client, for tooling — needs to know which one
this is.
method: "GET",
path: "/weather"
Route path with :param segments, e.g. "/orders/:id/cancel".
Must start with /, contain no empty segments and no trailing slash;
a malformed literal is a compile error.
path: "/weather",
handler: (ctx: {
readonlyreq:Request& {
readonlycookies?:Bun.CookieMap;
};
readonlyserver:Bun.Server<unknown>;
readonlyout:Outgoing;
readonlyroute:RouteInfo;
readonlystartedAt:number;
readonlyparams: {};
}) =>Promise<any>
The endpoint logic; ctx is fully inferred, never annotate it.
The return type is inferred rather than demanded, and checked twice
over. Against the route's own contract, by the
HandlerResult
bound on R: answering with something response never declared is a
compile error that says so, instead of a structural diff against
Response. And against what the framework can serialize at all, by
the intersected
ValidateResult
: a stream handed over bare is
refused whether or not the route declared anything, because that is
the case no contract covers — without a response schema
HandlerResult is unknown and accepts every value there is.