@tetsujs/core is the framework: controllers, routes, lifecycle hooks,
validation, cookies, WebSockets and the request pipeline. It has no runtime
dependencies. This page lists what the package exports and where each part
is explained. The model itself is in Key concepts.
It needs Bun 1.4 or later, TypeScript 5.7 or later with strict on,
@types/bun, and moduleResolution set to bundler, node16 or
nodenext. Installation explains each.
The package has two entry points: @tetsujs/core for the framework and
@tetsujs/core/testing for test helpers.
isRoute, isGroup and isWs tell whether a value was declared by
route(), group() or ws(). They are for code that walks a route tree,
such as a documentation generator.
Everything else is a type: the application, the context at each stage and
Requires, hooks and slots, routes, errors and cookies. The
createApp reference lists them by
area.
@tetsujs/core/testing imports bun:test, so use it in test files only.
Testing shows each helper in use.
Helper
What it does
serve(app, options?)
Starts the application on a free port and returns a request function for it, with the server’s address as request.url. Stopped when the tests around the call finish, so not to be called in beforeAll; { stop: false } leaves the stop to request.stop().
request.client(options?)
A client with default headers and a cookie jar, for a test that signs in and acts as that user.
request.stop()
Stops the server now. A request to it afterwards throws, saying what stopped it.
testCtx(parts, options?)
Builds a context for calling a handler directly, with no server.
captureErrors()
Collects what the framework logs on console.error, so a test can assert on it. Call it in a describe body, not inside a test.
stopServers()
Stops every server serve started that is still running. Needed only outside bun test.
constusers=controller("Users", () => ({
list: 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: "/users"
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: "/users",
handler: (ctx: {
readonlyreq:Request& {
readonlycookies?:Bun.CookieMap;
};
readonlyserver:Bun.Server<unknown>;
readonlyout:Outgoing;
readonlyroute:RouteInfo;
readonlystartedAt:number;
readonlyparams: {};
}) => never[]
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.