TypeScript Type Safety and tRPC Contracts
Build precise TypeScript models, validated boundaries, and tRPC contracts with compile-time checks at real call sites.
Before & after
Same prompt, same agent — the only difference is the skill.
End-to-end type safety is a great goal. Here are the main approaches: - **Share types** — put your types in a shared package both server and client import - **tRPC** — gives you full type inference without code generation - **GraphQL with codegen** — generate types from your schema - **OpenAPI with a generator** — generate a typed client from your spec - **Zod** — validate at runtime and infer types tRPC is popular because it infers types across the boundary without a separate schema or codegen step. You define procedures on the server and call them on the client with full autocomplete. Which fits your stack?
The gap in most "type-safe" APIs: the types are shared, but nothing
validates the wire. A malformed response still type-checks and then
crashes at runtime. Close both.
tRPC — inference across the boundary, no codegen
server/router.ts
export const appRouter = router({
getUser: publicProcedure
.input(z.object({ id: z.string().uuid() }))
.query(({ input }) => db.user.findUnique({ where: input })),
})
export type AppRouter = typeof appRouter // the only export the client needs
client.ts
const user = await trpc.getUser.query({ id })
// ^? fully inferred. Rename a server field → client fails to compile.
WHY ZOD ON THE INPUT MATTERS
The .input(z...) isn't decoration — it validates at the boundary AND
infers the type from the same declaration. One source, both guarantees.
Without it you have compile-time safety over runtime lies.
BRANDED TYPES — stop mixing up your ids
type UserId = string & { readonly __brand: 'UserId' }
const asUserId = (s: string): UserId => userIdSchema.parse(s) as UserId
Now getPost(userId) won't compile — a plain string, or a PostId, is
rejected. This catches the class of bug that end-to-end inference
alone doesn't: correctly-typed values used in the wrong slot.
DISCRIMINATED UNION for results, not thrown errors
type Result<T> =
| { ok: true; data: T }
| { ok: false; error: 'not_found' | 'forbidden' }
The client must narrow on .ok before touching .data — the compiler
makes the error case unignorable instead of a runtime surprise.
THE TSCONFIG LINE PEOPLE FORGET
"noUncheckedIndexedAccess": true
Without it, arr[0] is typed as T, not T | undefined, and every
"type-safe" codebase has this latent hole.
Which layer do you want built out — the router, or the branded-id
boundary?About this skill
“The types compile, but the wrong ID still gets through.” “Our generic loses inference.” “The tRPC client accepts a response the server never validates.” This skill traces those failures to their source and produces precise TypeScript models, boundary validation, and contracts that hold up at actual call sites.
What it does
- Replaces optional-field state models with discriminated unions and exhaustive handling.
- Repairs generic inference, conditional-type distribution, and mapped-type behavior using positive and negative type examples.
- Creates branded identifiers with validated constructors so unrelated strings cannot be mixed accidentally.
- Audits custom type guards for what they actually prove about unknown input.
- Builds tRPC procedures with inferred inputs, deliberate output validation, and separate authorization checks.
- Diagnoses monorepo contract drift, declaration leaks, and server code entering client dependencies.
What you get
A focused fix or complete implementation with explicit assumptions, representative usage, compile-time rejection cases, and runtime boundary checks. Verification results distinguish checks actually run from checks supplied for you to run.
Best for
TypeScript application and library developers fixing inference problems, modeling complex states, or connecting typed clients to validated tRPC APIs.
SKILL.md preview
---
name: typescript-type-safety-and-trpc-contracts
description: Builds and repairs TypeScript models, generics, branded identifiers, and tRPC contracts by tracing inference through call sites and validating runtime boundaries. Use when the user says "fix this TypeScript error", "my generic keeps widening to string", "make these states impossible to mix", "stop user IDs and organization IDs getting swapped", "is this type guard sound?", "set up end-to-end types with tRPC", or "our shared types keep drifting".
---
# TypeScript Type Safety and tRPC Contracts
… (sign up to view the full skill)More development skills
View all Development skills →SQL Query Optimization and Schema Review
Get SQL rewrites, evidence-based index recommendations, schema reviews, and dialect migrations with validation steps.
C# API and Blazor Implementation
Build and fix C# APIs and Blazor components with safe EF Core queries, cancellation, and focused tests.
Rust Code Implementation and Debugging
Get Rust code and targeted fixes for ownership, lifetimes, traits, Tokio concurrency, errors, and unsafe boundaries.
Type-Safe PHP Applications and APIs
Build and repair typed PHP applications with Laravel or Symfony, safe database changes, and targeted tests.