Type safety
One router definition types both the server handlers and the client hook — and the compiler keeps them in sync.
upload-stuff has a single source of truth: the file router you define on the
server. The server handlers (middleware, onUploadComplete) and the client
hook (useUploadStuff) all read their types from it, so the two ends cannot
drift — change the router and every call site updates or fails to compile.
Every block on this page is type-checked at build time, so the hovers below are the real inferred types, not hand-written annotations.
One definition, both ends
Hover the // ^? lines — those types are inferred, not declared.
.middleware()'s return is exactly the middlewareData that
.onUploadComplete() receives:
const = ({ : true, : ["image/*"], : "4MB" })
.(({ }) => ({ : . }))
.(({ middlewareData }) => { return { : . };
})
.();.input() works the same way: the schema's parsed output is what every
later step receives. Here the schema transforms a comma-separated string, so
input.tags is a string[]:
const = ({ : true, : ["image/*"], : "4MB" })
.(.({ : .().(() => .(",")) }))
.(({ input }) => { return { : . };
})
.();On the client, onClientUploadComplete receives the onUploadComplete return
as res.serverData — no annotation anywhere:
const = <>({
: "https://example.com",
});
const { } = ();
(() => ., {
: () => {
.(.serverData); },
});The (r) => r.avatar selector is more than a string: because the route name is a
real property, Go to Definition on it jumps straight to the route's server
definition.
What the compiler rejects
Two examples: .middleware() returned { uploadedBy }, so reading
middlewareData.role fails — and the selector is type-checked against the
router, so the misspelled r.avatr is rejected:
const = {
: ({ : true, : ["image/*"], : "4MB" })
.(({ }) => ({ : . }))
.(({ }) => {
return { : .role }; })
.(),
};
const = <typeof >({
: "https://example.com",
});
const { } = ();
(() => .avatr);One ordering rule: each builder method reads the current type, so write the chain as
.input().middleware().fields().onUploadComplete().build()—.fields()after.middleware()is what lets its resolver seemiddlewareData, and.onUploadComplete()sees everything before it.