{"slug":"typescript-advanced-types","title":"TypeScript Advanced Types: Generics, Utility Types, and Type Guards","tags":["typescript","generics","type-safety","utility-types","full-stack"],"agent_summary":"Production TypeScript patterns: conditional types, mapped types, template literal types, branded types, discriminated unions, and full-stack type safety with Zod.","trigger_phrases":["TypeScript generics","conditional types","mapped types","branded types","discriminated unions","Zod type inference","TypeScript utility types"],"runnable":false,"markdown":"\n## Overview\n\nAdvanced TypeScript for building robust, type-safe applications. These patterns appear repeatedly in Next.js, Convex, and API integrations.\n\n## Strict Configuration (Non-Negotiable)\n\n```json\n{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"exactOptionalPropertyTypes\": true,\n    \"noImplicitReturns\": true\n  }\n}\n```\n\n`noUncheckedIndexedAccess` is the most impactful non-default flag — catches `array[0]` being `undefined` at compile time.\n\n## Conditional Types\n\n```typescript\ntype NonNullable<T> = T extends null | undefined ? never : T;\ntype ReturnType<T> = T extends (...args: unknown[]) => infer R ? R : never;\ntype IsArray<T> = T extends unknown[] ? true : false;\n\n// Distribute over union members\ntype Flatten<T> = T extends Array<infer U> ? U : T;\n// Flatten<string[]>        → string\n// Flatten<(string|number)[]> → string | number\n```\n\n## Mapped Types\n\n```typescript\n// Make all properties optional and nullable\ntype Nullable<T> = { [K in keyof T]: T[K] | null };\n\n// Pick only function properties\ntype Methods<T> = {\n  [K in keyof T as T[K] extends Function ? K : never]: T[K];\n};\n\n// Rename keys with template literals\ntype Getters<T> = {\n  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];\n};\n```\n\n## Branded Types (Opaque Primitives)\n\nPrevent mixing of semantically different strings/numbers:\n\n```typescript\ntype UserId = string & { readonly __brand: \"UserId\" };\ntype PostId = string & { readonly __brand: \"PostId\" };\n\nfunction createUserId(id: string): UserId { return id as UserId; }\n\nfunction getUser(id: UserId) { /* ... */ }\n\nconst userId = createUserId(\"user_123\");\nconst postId = \"post_456\" as PostId;\n\ngetUser(userId);  // OK\ngetUser(postId);  // Type error — cannot assign PostId to UserId\n```\n\n## Discriminated Unions (Error Handling)\n\n```typescript\ntype Result<T, E = Error> =\n  | { success: true; data: T }\n  | { success: false; error: E };\n\nfunction parseUser(raw: unknown): Result<User> {\n  const parsed = UserSchema.safeParse(raw);\n  if (!parsed.success) return { success: false, error: parsed.error };\n  return { success: true, data: parsed.data };\n}\n\n// At call site — exhaustive narrowing\nconst result = parseUser(rawData);\nif (result.success) {\n  console.log(result.data.name);  // data is typed as User\n} else {\n  console.error(result.error);    // error is typed as ZodError\n}\n```\n\n## Zod — Runtime Validation + Type Inference\n\n```typescript\nimport { z } from \"zod\";\n\nconst UserSchema = z.object({\n  id: z.string().uuid(),\n  email: z.string().email(),\n  role: z.enum([\"admin\", \"user\", \"guest\"]),\n  createdAt: z.string().datetime(),\n});\n\ntype User = z.infer<typeof UserSchema>;  // TypeScript type from schema\n\n// Validate at API boundary\nexport async function POST(req: Request) {\n  const body = await req.json();\n  const parsed = UserSchema.safeParse(body);\n  if (!parsed.success) {\n    return Response.json({ error: parsed.error.flatten() }, { status: 400 });\n  }\n  // parsed.data is fully typed as User\n}\n```\n\n## Full-Stack Type Safety Pattern\n\nShare types between frontend and backend:\n\n```typescript\n// types/api.ts — shared between client and server\nexport interface ApiResponse<T> {\n  data: T;\n  meta: { page: number; total: number };\n}\n\nexport type UserListResponse = ApiResponse<User[]>;\n\n// Server (route.ts)\nexport async function GET(): Promise<Response> {\n  const users = await db.query.users.findMany();\n  const payload: UserListResponse = { data: users, meta: { page: 1, total: users.length } };\n  return Response.json(payload);\n}\n\n// Client\nconst res = await fetch(\"/api/users\");\nconst json: UserListResponse = await res.json(); // Typed at consumption\n```\n\n## Template Literal Types\n\n```typescript\ntype EventName = `on${Capitalize<string>}`;\ntype CSSProperty = `${string}-${string}`;\ntype Route = `/api/${string}`;\n\n// Strongly typed event bus\ntype EventMap = {\n  \"user:created\": { userId: string };\n  \"user:deleted\": { userId: string };\n  \"post:published\": { postId: string; authorId: string };\n};\n\nfunction emit<K extends keyof EventMap>(event: K, payload: EventMap[K]) { /* */ }\nemit(\"user:created\", { userId: \"123\" });          // OK\nemit(\"user:created\", { postId: \"123\" });          // Error — wrong payload shape\n```\n\n## Type Guard Functions\n\n```typescript\nfunction isUser(value: unknown): value is User {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"id\" in value &&\n    \"email\" in value\n  );\n}\n\n// Assertion function (throws on failure)\nfunction assertUser(value: unknown): asserts value is User {\n  if (!isUser(value)) throw new TypeError(\"Expected User\");\n}\n```\n","html":"<h2>Overview</h2>\n<p>Advanced TypeScript for building robust, type-safe applications. These patterns appear repeatedly in Next.js, Convex, and API integrations.</p>\n<h2>Strict Configuration (Non-Negotiable)</h2>\n<pre><code class=\"language-json\">{\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"noUncheckedIndexedAccess\": true,\n    \"exactOptionalPropertyTypes\": true,\n    \"noImplicitReturns\": true\n  }\n}\n</code></pre>\n<p><code>noUncheckedIndexedAccess</code> is the most impactful non-default flag — catches <code>array[0]</code> being <code>undefined</code> at compile time.</p>\n<h2>Conditional Types</h2>\n<pre><code class=\"language-typescript\">type NonNullable&#x3C;T> = T extends null | undefined ? never : T;\ntype ReturnType&#x3C;T> = T extends (...args: unknown[]) => infer R ? R : never;\ntype IsArray&#x3C;T> = T extends unknown[] ? true : false;\n\n// Distribute over union members\ntype Flatten&#x3C;T> = T extends Array&#x3C;infer U> ? U : T;\n// Flatten&#x3C;string[]>        → string\n// Flatten&#x3C;(string|number)[]> → string | number\n</code></pre>\n<h2>Mapped Types</h2>\n<pre><code class=\"language-typescript\">// Make all properties optional and nullable\ntype Nullable&#x3C;T> = { [K in keyof T]: T[K] | null };\n\n// Pick only function properties\ntype Methods&#x3C;T> = {\n  [K in keyof T as T[K] extends Function ? K : never]: T[K];\n};\n\n// Rename keys with template literals\ntype Getters&#x3C;T> = {\n  [K in keyof T as `get${Capitalize&#x3C;string &#x26; K>}`]: () => T[K];\n};\n</code></pre>\n<h2>Branded Types (Opaque Primitives)</h2>\n<p>Prevent mixing of semantically different strings/numbers:</p>\n<pre><code class=\"language-typescript\">type UserId = string &#x26; { readonly __brand: \"UserId\" };\ntype PostId = string &#x26; { readonly __brand: \"PostId\" };\n\nfunction createUserId(id: string): UserId { return id as UserId; }\n\nfunction getUser(id: UserId) { /* ... */ }\n\nconst userId = createUserId(\"user_123\");\nconst postId = \"post_456\" as PostId;\n\ngetUser(userId);  // OK\ngetUser(postId);  // Type error — cannot assign PostId to UserId\n</code></pre>\n<h2>Discriminated Unions (Error Handling)</h2>\n<pre><code class=\"language-typescript\">type Result&#x3C;T, E = Error> =\n  | { success: true; data: T }\n  | { success: false; error: E };\n\nfunction parseUser(raw: unknown): Result&#x3C;User> {\n  const parsed = UserSchema.safeParse(raw);\n  if (!parsed.success) return { success: false, error: parsed.error };\n  return { success: true, data: parsed.data };\n}\n\n// At call site — exhaustive narrowing\nconst result = parseUser(rawData);\nif (result.success) {\n  console.log(result.data.name);  // data is typed as User\n} else {\n  console.error(result.error);    // error is typed as ZodError\n}\n</code></pre>\n<h2>Zod — Runtime Validation + Type Inference</h2>\n<pre><code class=\"language-typescript\">import { z } from \"zod\";\n\nconst UserSchema = z.object({\n  id: z.string().uuid(),\n  email: z.string().email(),\n  role: z.enum([\"admin\", \"user\", \"guest\"]),\n  createdAt: z.string().datetime(),\n});\n\ntype User = z.infer&#x3C;typeof UserSchema>;  // TypeScript type from schema\n\n// Validate at API boundary\nexport async function POST(req: Request) {\n  const body = await req.json();\n  const parsed = UserSchema.safeParse(body);\n  if (!parsed.success) {\n    return Response.json({ error: parsed.error.flatten() }, { status: 400 });\n  }\n  // parsed.data is fully typed as User\n}\n</code></pre>\n<h2>Full-Stack Type Safety Pattern</h2>\n<p>Share types between frontend and backend:</p>\n<pre><code class=\"language-typescript\">// types/api.ts — shared between client and server\nexport interface ApiResponse&#x3C;T> {\n  data: T;\n  meta: { page: number; total: number };\n}\n\nexport type UserListResponse = ApiResponse&#x3C;User[]>;\n\n// Server (route.ts)\nexport async function GET(): Promise&#x3C;Response> {\n  const users = await db.query.users.findMany();\n  const payload: UserListResponse = { data: users, meta: { page: 1, total: users.length } };\n  return Response.json(payload);\n}\n\n// Client\nconst res = await fetch(\"/api/users\");\nconst json: UserListResponse = await res.json(); // Typed at consumption\n</code></pre>\n<h2>Template Literal Types</h2>\n<pre><code class=\"language-typescript\">type EventName = `on${Capitalize&#x3C;string>}`;\ntype CSSProperty = `${string}-${string}`;\ntype Route = `/api/${string}`;\n\n// Strongly typed event bus\ntype EventMap = {\n  \"user:created\": { userId: string };\n  \"user:deleted\": { userId: string };\n  \"post:published\": { postId: string; authorId: string };\n};\n\nfunction emit&#x3C;K extends keyof EventMap>(event: K, payload: EventMap[K]) { /* */ }\nemit(\"user:created\", { userId: \"123\" });          // OK\nemit(\"user:created\", { postId: \"123\" });          // Error — wrong payload shape\n</code></pre>\n<h2>Type Guard Functions</h2>\n<pre><code class=\"language-typescript\">function isUser(value: unknown): value is User {\n  return (\n    typeof value === \"object\" &#x26;&#x26;\n    value !== null &#x26;&#x26;\n    \"id\" in value &#x26;&#x26;\n    \"email\" in value\n  );\n}\n\n// Assertion function (throws on failure)\nfunction assertUser(value: unknown): asserts value is User {\n  if (!isUser(value)) throw new TypeError(\"Expected User\");\n}\n</code></pre>\n"}