Skip to content
fossyl

Type-Safe Routes

Fossyl’s type system is designed to give you compile-time guarantees about your API routes. Every URL parameter, query string, request body, and authentication context is fully typed — no explicit generics required.

Route paths use :param syntax for dynamic segments. Fossyl extracts these parameters at the type level and exposes them as typed fields on the url object.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _userRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/users/:id")
.get<Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ url{ id: string } & { readonly __kind: "url" } }) => async () => {
return { typeName: "User", id: url{ id: string } & { readonly __kind: "url" }.idstring, name: "John Doe" };
});
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/posts/:postId/comments/:commentId")
.get<Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => () => Promise<Response>, ) => Route(({ url{ id: string } & { readonly __kind: "url" } }) => async () => {
return { typeName: "Comment", postId: url{ id: string } & { readonly __kind: "url" }.postIdstring, commentId: url{ id: string } & { readonly __kind: "url" }.commentIdstring };
});

The Params<Path> utility type powers this — it parses the path template at the type level and produces an object type like { id: string } or { postId: string; commentId: string }.

Every HTTP method on an endpoint uses function overloads to give you exactly the configuration options you need — and only those. The type system guides you toward correct usage.

Open Routes — No Auth, No Body Validation

Section titled “Open Routes — No Auth, No Body Validation”

For public GET/DELETE endpoints. The handler receives URL params and optionally parsed query params.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/health")
.get<Response extends ResponseData>( handler: () => Promise<Response>, ) => Route(async () => {
return { typeName: "Health", status: "ok" };
});
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/search")
.querystring((data) => data as { qstring: string; limitnumber | undefined?: number })
.get<Response extends ResponseData>( handler: () => Promise<Response>, ) => Route(({ querystring }) => async () => {
return { typeName: "SearchResults", results: [], query: querystring.qstring };
});

Validated Routes — Body Validation, No Auth

Section titled “Validated Routes — Body Validation, No Auth”

For POST/PUT endpoints where anyone can submit data, but the body must be validated.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath> } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/contact")
.validator<RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route }((data) => {
const { namestring, emailstring } = data as { namestring: string; emailstring: string };
if (!namestring || !emailstring) throw new Error("Name and email required");
return { namestring, emailstring };
})
.post<Response extends ResponseData>( handler: ( body: { name: string; email: string } & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route((_body{ name: string; email: string } & { readonly __kind: "body" }) => async () => {
return { typeName: "MessageSent", ok: true };
});

The validator function’s return type becomes the body type in the handler — inference flows through automatically.

Authenticated Routes — Auth, No Body Validation

Section titled “Authenticated Routes — Auth, No Body Validation”

For GET/DELETE endpoints that require an authenticated user.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath>, authWrapper<T>(auth: T) => T & Authentication } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
const _userRouteRoute = { path: string method: RestMethod steps: Steps[] handler: Function authenticator?: AuthenticationFunction<any> validator?: ValidatorFunction<any> queryValidator?: ValidatorFunction<any> urlParamValidator?: ValidatorFunction<any> paginationConfig?: PaginationConfig hasTransaction: boolean } = routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/users/:id")
.authenticator<Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route }(async (headersRecord<string, string>) => {
const userIdstring = headersRecord<string, string>["x-user-id"];
if (!userIdstring) throw new Error("Unauthorized");
return authWrapper<T>(auth: T) => T & Authentication({ userIdstring, role: "admin" });
})
.get<Response extends ResponseData>( handler: ( parameters: { url: { id: string } & { readonly __kind: "url" } } & { readonly __kind: "parameters" }, ) => ( auth: { userId: string; role: string } & Authentication & { readonly __kind: "auth" }, ) => () => Promise<Response>, ) => Route(({ url{ id: string } & { readonly __kind: "url" } }) => (_auth{ userId: string; role: string } & Authentication & { readonly __kind: "auth" }) => async () => {
return { typeName: "UserProfile", id: url{ id: string } & { readonly __kind: "url" }.idstring };
});

For POST/PUT endpoints that need both authentication and a validated body.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath>, authWrapper<T>(auth: T) => T & Authentication } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/posts")
.authenticator<Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends Auth ? undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route } } & { get: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route post: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route delete: <Response extends ResponseData>( handler: undefined extends Auth ? () => Promise<Response> : (auth: Auth & { readonly __kind: "auth" }) => () => Promise<Response>, ) => Route }(async (headersRecord<string, string>) => {
return authWrapper<T>(auth: T) => T & Authentication({ userId: headersRecord<string, string>["x-user-id"] });
})
.validator<RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route put: <Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<Response> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route }((data) => {
const { titlestring, contentstring } = data as { titlestring: string; contentstring: string };
if (!titlestring) throw new Error("Title required");
return { titlestring, contentstring };
})
.post<Response extends ResponseData>( handler: ( auth: { userId: string } & Authentication & { readonly __kind: "auth" }, ) => ( body: { title: string; content: string } & { readonly __kind: "body" }, ) => () => Promise<Response>, ) => Route((_auth{ userId: string } & Authentication & { readonly __kind: "auth" }) => (body{ title: string; content: string } & { readonly __kind: "body" }) => async () => {
return { typeName: "PostCreated", id: "new-id", ...body{ title: string; content: string } & { readonly __kind: "body" } };
});

For collection endpoints that return paginated results. Pagination params (page, pageSize) are automatically parsed from the query string.

// Hover any identifier to see its type
import { createRouter<BasePath extends string>(_: BasePath) => Router<BasePath>, authWrapper<T>(auth: T) => T & Authentication } from "@fossyl/core";
const routerRouter<"/api"> = createRouter<BasePath extends string>(_: BasePath) => Router<BasePath><"/api">("/api");
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/users")
.paginate( paginationConfig: PaginationConfig, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } authenticator: <Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route }({
defaultPageSize: 20,
maxPageSize: 100,
})
.get<Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route(({ pagination{ page: number; pageSize: number; hasMore: false } }) => async () => {
return {
data: [{ typeName: "User", id: "1", name: "Alice" }],
pagination: {
page: pagination{ page: number; pageSize: number; hasMore: false }.pagenumber,
pageSize: pagination{ page: number; pageSize: number; hasMore: false }.pageSizenumber,
hasMore: false,
},
};
});
routerRouter<"/api">
.createEndpoint<Path extends `/api${string}`>(path: Path) => Endpoint<Path, true>("/api/admin/users")
.query{ role?: string } & { readonly __kind: "query" }((datanever[]) => datanever[] as { rolestring?: string })
.paginate( paginationConfig: PaginationConfig, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } authenticator: <Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route }({
defaultPageSize: 50,
maxPageSize: 200,
})
.authenticator<Auth extends Authentication>( authenticationFunction: AuthenticationFunction<Auth>, ) => { validator: <RequestBody extends unknown>( validatorFunction: ValidatorFunction<RequestBody>, ) => { post: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => undefined extends RequestBody ? () => Promise<PaginatedResponse<Response>> : ( body: RequestBody & { readonly __kind: "body" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route } } & { get: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route post: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route put: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route delete: <Response extends ResponseData>( handler: ( parameters: { query: { role?: string } & { readonly __kind: "query" } pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => undefined extends Auth ? () => Promise<PaginatedResponse<Response>> : ( auth: Auth & { readonly __kind: "auth" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route }(async (headersRecord<string, string>) => authWrapper<T>(auth: T) => T & Authentication({ role: headersRecord<string, string>["x-role"] }))
.get<Response extends ResponseData>( handler: ( parameters: { pagination: PaginationParams & { readonly __kind: "pagination" } } & { readonly __kind: "parameters" }, ) => () => Promise<PaginatedResponse<Response>>, ) => Route(({ query{ role?: string } & { readonly __kind: "query" }: _query{ role?: string } & { readonly __kind: "query" }, pagination{ page: number; pageSize: number; hasMore: false } }) => (_auth{ role: string } & Authentication & { readonly __kind: "auth" }) => async () => {
return {
data: [],
pagination: { page: pagination{ page: number; pageSize: number; hasMore: false }.pagenumber, pageSize: pagination{ page: number; pageSize: number; hasMore: false }.pageSizenumber, hasMore: false },
};
});

List routes return PaginatedResponse<T> — a { data: T[], pagination: { page, pageSize, hasMore?, total? } } shape.

Fossyl uses a few key techniques to drive type inference without explicit generics:

Each method (.get(), .post(), etc.) has multiple overloads ordered by specificity. The TypeScript compiler picks the correct overload based on which keys you provide in the config object:

  • No authenticator, no queryValidator → Open route (GET/DELETE) or Validated route (POST/PUT)
  • authenticator only → Authenticated route
  • validator only → Validated route
  • authenticator + validator → Full route
  • .list() → List route (pagination always included)

Overloads with queryValidator are listed first so they take priority when both query and no-query variants match.

The Authentication and RequestBody types use unique symbols as brands:

declare const authBrand: unique symbol;
type Authentication = { readonly [authBrand]: "Auth" };
declare const requestBrand: unique symbol;
type RequestBody = { readonly [requestBrand]: "RequestBody" };

authWrapper() and bodyWrapper() apply these brands at runtime. The brands are what let the type system distinguish between “data from the authenticator” and “data from the validator” — and pass them to the correct handler parameter positions.

All route handlers must return objects that include a typeName property:

type ResponseData<TypeName extends string = string> = {
typeNameTypeName: TypeName;
};

This enables self-describing API responses. The ApiResponse wrapper automatically includes the typeName as a type field on the outer envelope:

typeT["typeName"] ApiResponse<T extends ResponseData> = {
success"true": "true";
typeT["typeName"]: T["typeName"];
dataT: T;
};

The Express adapter (and others) wrap all responses in this format automatically.

Fossyl leaves error handling to you. Throw from authenticator, validator, or handler and handle it in your adapter’s error middleware. This keeps the type system focused on what matters: correct data flow at compile time.