advanced-types.ts raw
1 /**
2 * Advanced TypeScript Types
3 *
4 * This file demonstrates advanced TypeScript features including:
5 * - Generics with constraints
6 * - Conditional types
7 * - Mapped types
8 * - Template literal types
9 * - Recursive types
10 * - Utility type implementations
11 */
12
13 // ============================================================================
14 // Generics Basics
15 // ============================================================================
16
17 // Generic function
18 function identity<T>(value: T): T {
19 return value
20 }
21
22 const stringValue = identity('hello') // Type: string
23 const numberValue = identity(42) // Type: number
24
25 // Generic interface
26 interface Box<T> {
27 value: T
28 }
29
30 const stringBox: Box<string> = { value: 'hello' }
31 const numberBox: Box<number> = { value: 42 }
32
33 // Generic class
34 class Stack<T> {
35 private items: T[] = []
36
37 push(item: T): void {
38 this.items.push(item)
39 }
40
41 pop(): T | undefined {
42 return this.items.pop()
43 }
44
45 peek(): T | undefined {
46 return this.items[this.items.length - 1]
47 }
48
49 isEmpty(): boolean {
50 return this.items.length === 0
51 }
52 }
53
54 const numberStack = new Stack<number>()
55 numberStack.push(1)
56 numberStack.push(2)
57 numberStack.pop() // Type: number | undefined
58
59 // ============================================================================
60 // Generic Constraints
61 // ============================================================================
62
63 // Constrain to specific type
64 interface HasLength {
65 length: number
66 }
67
68 function logLength<T extends HasLength>(item: T): void {
69 console.log(item.length)
70 }
71
72 logLength('string') // OK
73 logLength([1, 2, 3]) // OK
74 logLength({ length: 10 }) // OK
75 // logLength(42) // Error: number doesn't have length
76
77 // Constrain to object keys
78 function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
79 return obj[key]
80 }
81
82 interface User {
83 id: string
84 name: string
85 age: number
86 }
87
88 const user: User = { id: '1', name: 'Alice', age: 30 }
89 const userName = getProperty(user, 'name') // Type: string
90 // const invalid = getProperty(user, 'invalid') // Error
91
92 // Multiple type parameters with constraints
93 function merge<T extends object, U extends object>(obj1: T, obj2: U): T & U {
94 return { ...obj1, ...obj2 }
95 }
96
97 const merged = merge({ a: 1 }, { b: 2 }) // Type: { a: number } & { b: number }
98
99 // ============================================================================
100 // Conditional Types
101 // ============================================================================
102
103 // Basic conditional type
104 type IsString<T> = T extends string ? true : false
105
106 type A = IsString<string> // true
107 type B = IsString<number> // false
108
109 // Nested conditional types
110 type TypeName<T> = T extends string
111 ? 'string'
112 : T extends number
113 ? 'number'
114 : T extends boolean
115 ? 'boolean'
116 : T extends undefined
117 ? 'undefined'
118 : T extends Function
119 ? 'function'
120 : 'object'
121
122 type T1 = TypeName<string> // "string"
123 type T2 = TypeName<number> // "number"
124 type T3 = TypeName<() => void> // "function"
125
126 // Distributive conditional types
127 type ToArray<T> = T extends any ? T[] : never
128
129 type StrArrOrNumArr = ToArray<string | number> // string[] | number[]
130
131 // infer keyword
132 type Flatten<T> = T extends Array<infer U> ? U : T
133
134 type Str = Flatten<string[]> // string
135 type Num = Flatten<number> // number
136
137 // Return type extraction
138 type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never
139
140 function exampleFn(): string {
141 return 'hello'
142 }
143
144 type ExampleReturn = MyReturnType<typeof exampleFn> // string
145
146 // Parameters extraction
147 type MyParameters<T> = T extends (...args: infer P) => any ? P : never
148
149 function createUser(name: string, age: number): User {
150 return { id: '1', name, age }
151 }
152
153 type CreateUserParams = MyParameters<typeof createUser> // [string, number]
154
155 // ============================================================================
156 // Mapped Types
157 // ============================================================================
158
159 // Make all properties optional
160 type MyPartial<T> = {
161 [K in keyof T]?: T[K]
162 }
163
164 interface Person {
165 name: string
166 age: number
167 email: string
168 }
169
170 type PartialPerson = MyPartial<Person>
171 // {
172 // name?: string
173 // age?: number
174 // email?: string
175 // }
176
177 // Make all properties required
178 type MyRequired<T> = {
179 [K in keyof T]-?: T[K]
180 }
181
182 // Make all properties readonly
183 type MyReadonly<T> = {
184 readonly [K in keyof T]: T[K]
185 }
186
187 // Pick specific properties
188 type MyPick<T, K extends keyof T> = {
189 [P in K]: T[P]
190 }
191
192 type UserProfile = MyPick<User, 'id' | 'name'>
193 // { id: string; name: string }
194
195 // Omit specific properties
196 type MyOmit<T, K extends keyof T> = {
197 [P in keyof T as P extends K ? never : P]: T[P]
198 }
199
200 type UserWithoutAge = MyOmit<User, 'age'>
201 // { id: string; name: string }
202
203 // Transform property types
204 type Nullable<T> = {
205 [K in keyof T]: T[K] | null
206 }
207
208 type NullablePerson = Nullable<Person>
209 // {
210 // name: string | null
211 // age: number | null
212 // email: string | null
213 // }
214
215 // ============================================================================
216 // Key Remapping
217 // ============================================================================
218
219 // Add prefix to keys
220 type Getters<T> = {
221 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
222 }
223
224 type PersonGetters = Getters<Person>
225 // {
226 // getName: () => string
227 // getAge: () => number
228 // getEmail: () => string
229 // }
230
231 // Filter keys by type
232 type PickByType<T, U> = {
233 [K in keyof T as T[K] extends U ? K : never]: T[K]
234 }
235
236 interface Model {
237 id: number
238 name: string
239 description: string
240 price: number
241 }
242
243 type StringFields = PickByType<Model, string>
244 // { name: string; description: string }
245
246 // Remove specific key
247 type RemoveKindField<T> = {
248 [K in keyof T as Exclude<K, 'kind'>]: T[K]
249 }
250
251 // ============================================================================
252 // Template Literal Types
253 // ============================================================================
254
255 // Event name generation
256 type EventName<T extends string> = `on${Capitalize<T>}`
257
258 type ClickEvent = EventName<'click'> // "onClick"
259 type SubmitEvent = EventName<'submit'> // "onSubmit"
260
261 // Combining literals
262 type Color = 'red' | 'green' | 'blue'
263 type Shade = 'light' | 'dark'
264 type ColorShade = `${Shade}-${Color}`
265 // "light-red" | "light-green" | "light-blue" | "dark-red" | "dark-green" | "dark-blue"
266
267 // CSS properties
268 type CSSProperty = 'margin' | 'padding'
269 type Side = 'top' | 'right' | 'bottom' | 'left'
270 type CSSPropertyWithSide = `${CSSProperty}-${Side}`
271 // "margin-top" | "margin-right" | ... | "padding-left"
272
273 // Route generation
274 type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
275 type Endpoint = '/users' | '/products' | '/orders'
276 type ApiRoute = `${HttpMethod} ${Endpoint}`
277 // "GET /users" | "POST /users" | ... | "DELETE /orders"
278
279 // ============================================================================
280 // Recursive Types
281 // ============================================================================
282
283 // JSON value type
284 type JSONValue = string | number | boolean | null | JSONObject | JSONArray
285
286 interface JSONObject {
287 [key: string]: JSONValue
288 }
289
290 interface JSONArray extends Array<JSONValue> {}
291
292 // Tree structure
293 interface TreeNode<T> {
294 value: T
295 children?: TreeNode<T>[]
296 }
297
298 const tree: TreeNode<number> = {
299 value: 1,
300 children: [
301 { value: 2, children: [{ value: 4 }, { value: 5 }] },
302 { value: 3, children: [{ value: 6 }] },
303 ],
304 }
305
306 // Deep readonly
307 type DeepReadonly<T> = {
308 readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
309 }
310
311 interface NestedConfig {
312 api: {
313 url: string
314 timeout: number
315 }
316 features: {
317 darkMode: boolean
318 }
319 }
320
321 type ImmutableConfig = DeepReadonly<NestedConfig>
322 // All properties at all levels are readonly
323
324 // Deep partial
325 type DeepPartial<T> = {
326 [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
327 }
328
329 // ============================================================================
330 // Advanced Utility Types
331 // ============================================================================
332
333 // Exclude types from union
334 type MyExclude<T, U> = T extends U ? never : T
335
336 type T4 = MyExclude<'a' | 'b' | 'c', 'a'> // "b" | "c"
337
338 // Extract types from union
339 type MyExtract<T, U> = T extends U ? T : never
340
341 type T5 = MyExtract<'a' | 'b' | 'c', 'a' | 'f'> // "a"
342
343 // NonNullable
344 type MyNonNullable<T> = T extends null | undefined ? never : T
345
346 type T6 = MyNonNullable<string | null | undefined> // string
347
348 // Record
349 type MyRecord<K extends keyof any, T> = {
350 [P in K]: T
351 }
352
353 type PageInfo = MyRecord<string, number>
354
355 // Awaited
356 type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T
357
358 type T7 = MyAwaited<Promise<string>> // string
359 type T8 = MyAwaited<Promise<Promise<number>>> // number
360
361 // ============================================================================
362 // Branded Types
363 // ============================================================================
364
365 type Brand<K, T> = K & { __brand: T }
366
367 type USD = Brand<number, 'USD'>
368 type EUR = Brand<number, 'EUR'>
369 type UserId = Brand<string, 'UserId'>
370 type ProductId = Brand<string, 'ProductId'>
371
372 function makeUSD(amount: number): USD {
373 return amount as USD
374 }
375
376 function makeUserId(id: string): UserId {
377 return id as UserId
378 }
379
380 const usd = makeUSD(100)
381 const userId = makeUserId('user-123')
382
383 // Type-safe operations
384 function addMoney(a: USD, b: USD): USD {
385 return (a + b) as USD
386 }
387
388 // Prevents mixing different branded types
389 // const total = addMoney(usd, eur) // Error
390
391 // ============================================================================
392 // Union to Intersection
393 // ============================================================================
394
395 type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
396 k: infer I,
397 ) => void
398 ? I
399 : never
400
401 type Union = { a: string } | { b: number }
402 type Intersection = UnionToIntersection<Union>
403 // { a: string } & { b: number }
404
405 // ============================================================================
406 // Advanced Generic Patterns
407 // ============================================================================
408
409 // Constraining multiple related types
410 function merge<
411 T extends Record<string, any>,
412 U extends Record<string, any>,
413 K extends keyof T & keyof U,
414 >(obj1: T, obj2: U, conflictKeys: K[]): T & U {
415 const result = { ...obj1, ...obj2 }
416 conflictKeys.forEach((key) => {
417 // Handle conflicts
418 })
419 return result as T & U
420 }
421
422 // Builder pattern with fluent API
423 class QueryBuilder<T, Selected extends keyof T = never> {
424 private selectFields: Set<keyof T> = new Set()
425
426 select<K extends keyof T>(
427 ...fields: K[]
428 ): QueryBuilder<T, Selected | K> {
429 fields.forEach((field) => this.selectFields.add(field))
430 return this as any
431 }
432
433 execute(): Pick<T, Selected> {
434 // Execute query
435 return {} as Pick<T, Selected>
436 }
437 }
438
439 // Usage
440 interface Product {
441 id: string
442 name: string
443 price: number
444 description: string
445 }
446
447 const result = new QueryBuilder<Product>()
448 .select('id', 'name')
449 .select('price')
450 .execute()
451 // Type: { id: string; name: string; price: number }
452
453 // ============================================================================
454 // Exports
455 // ============================================================================
456
457 export type {
458 Box,
459 HasLength,
460 IsString,
461 Flatten,
462 MyPartial,
463 MyRequired,
464 MyReadonly,
465 Nullable,
466 DeepReadonly,
467 DeepPartial,
468 Brand,
469 USD,
470 EUR,
471 UserId,
472 ProductId,
473 JSONValue,
474 TreeNode,
475 }
476
477 export { Stack, identity, getProperty, merge, makeUSD, makeUserId }
478
479