Why TypeScript
Types are the contract that keeps decorators honest and carries a shape across every boundary.
Why TypeScript
Base is written in TypeScript, and your Base application is too. That is not just a language preference. TypeScript is load-bearing in how Base works: the type system is the contract that holds the framework's many parts together, and the editor's red squiggle is the first line of defense against a whole class of bugs that other stacks only catch in production.
Types are the contract between the parts
A Base application is made of typed classes that talk to each other across boundaries: a resolver calls a service, a service uses a repository, one worker calls another over RPC, a browser calls a worker. In a loosely typed stack, each of those boundaries is a place where the two sides can quietly disagree. The caller sends a field the receiver renamed last week, and nothing complains until a request fails.
Base uses types to close those gaps. A repository knows the entity it manages, so the rows it returns are typed and the fields you query are checked. An RPC client is typed against the service it calls, so calling a procedure that changed shape is a compile error, not a 500. A GraphQL resolver's return type and its declared schema type line up, because both come from the same class. The contract between the parts is the type, and the compiler checks it for you every time you save.
The type carries through, end to end
The payoff compounds when the same type flows across a boundary instead of being redeclared on each side. Base is built so a type defined once can be shared: the shape your worker exposes over RPC is the shape the caller sees, the entity you define is the entity your repository returns, the validated input class is the same class your resolver receives. You are not maintaining parallel definitions that drift. You change the type in one place and every consumer of it updates its expectations at compile time.
That is what "typed end to end" actually buys you. Not just autocomplete, though you get that. It is that a rename, a new required field, or a changed return type surfaces immediately, everywhere it matters, before the code runs. The class of bug where two parts of a system silently fall out of agreement mostly stops existing.
Types make the decorators honest
Base describes a lot of your application through decorators: a column, a route, a validated field, an injected dependency. TypeScript is what keeps those descriptions tied to reality. When you inject a dependency, the token carries its type, so the thing you receive matches the thing you asked for. Base even ships a lint rule that checks the injected type matches the parameter it lands in. The decorator says what something is; the type system makes sure the code around it agrees.
This is why bare, untyped tokens are deliberately not allowed as injection keys. Every injection point keeps its static type, so the wiring Base derives from your decorators is wiring the compiler can verify. The description and the implementation cannot drift apart, because the type binds them.
Why it matters for you
Choosing TypeScript is choosing to find out about a mismatch in your editor instead of in your logs. Base leans on that all the way down: typed repositories, typed RPC, typed GraphQL, typed injection, validated inputs whose shape is a real type. You write less defensive glue, you refactor with the compiler watching your back, and the boundaries between your application's parts hold because the type system is holding them. The result is the framework's core promise, applied to correctness: the pieces fit, and the fit is checked.