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.
URL Parameter Inference
Section titled “URL Parameter Inference”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 typeimport { 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 }.
Six Route Types, One API
Section titled “Six Route Types, One API”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 typeimport { 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 typeimport { 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 typeimport { 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 }; });Full Routes — Auth + Body Validation
Section titled “Full Routes — Auth + Body Validation”For POST/PUT endpoints that need both authentication and a validated body.
// Hover any identifier to see its typeimport { 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" } }; });List Routes — Paginated GET Endpoints
Section titled “List Routes — Paginated GET Endpoints”For collection endpoints that return paginated results. Pagination params (page, pageSize) are automatically parsed from the query string.
// Hover any identifier to see its typeimport { 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.
How Inference Works
Section titled “How Inference Works”Fossyl uses a few key techniques to drive type inference without explicit generics:
Function Overloads
Section titled “Function Overloads”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, noqueryValidator→ Open route (GET/DELETE) or Validated route (POST/PUT) authenticatoronly → Authenticated routevalidatoronly → Validated routeauthenticator+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.
Branded Types
Section titled “Branded Types”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.
Response Data Constraint
Section titled “Response Data Constraint”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.
Error Handling
Section titled “Error Handling”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.