type-system-basics.ts raw
1 /**
2 * TypeScript Type System Basics
3 *
4 * This file demonstrates fundamental TypeScript concepts including:
5 * - Primitive types
6 * - Object types (interfaces, type aliases)
7 * - Union and intersection types
8 * - Type inference and narrowing
9 * - Function types
10 */
11
12 // ============================================================================
13 // Primitive Types
14 // ============================================================================
15
16 const message: string = 'Hello, TypeScript!'
17 const count: number = 42
18 const isActive: boolean = true
19 const nothing: null = null
20 const notDefined: undefined = undefined
21
22 // ============================================================================
23 // Object Types
24 // ============================================================================
25
26 // Interface definition
27 interface User {
28 id: string
29 name: string
30 email: string
31 age?: number // Optional property
32 readonly createdAt: Date // Readonly property
33 }
34
35 // Type alias definition
36 type Product = {
37 id: string
38 name: string
39 price: number
40 category: string
41 }
42
43 // Creating objects
44 const user: User = {
45 id: '1',
46 name: 'Alice',
47 email: 'alice@example.com',
48 createdAt: new Date(),
49 }
50
51 const product: Product = {
52 id: 'p1',
53 name: 'Laptop',
54 price: 999,
55 category: 'electronics',
56 }
57
58 // ============================================================================
59 // Union Types
60 // ============================================================================
61
62 type Status = 'idle' | 'loading' | 'success' | 'error'
63 type ID = string | number
64
65 function formatId(id: ID): string {
66 if (typeof id === 'string') {
67 return id.toUpperCase()
68 }
69 return id.toString()
70 }
71
72 // Discriminated unions
73 type ApiResponse =
74 | { success: true; data: User }
75 | { success: false; error: string }
76
77 function handleResponse(response: ApiResponse) {
78 if (response.success) {
79 // TypeScript knows response.data exists here
80 console.log(response.data.name)
81 } else {
82 // TypeScript knows response.error exists here
83 console.error(response.error)
84 }
85 }
86
87 // ============================================================================
88 // Intersection Types
89 // ============================================================================
90
91 type Timestamped = {
92 createdAt: Date
93 updatedAt: Date
94 }
95
96 type TimestampedUser = User & Timestamped
97
98 const timestampedUser: TimestampedUser = {
99 id: '1',
100 name: 'Bob',
101 email: 'bob@example.com',
102 createdAt: new Date(),
103 updatedAt: new Date(),
104 }
105
106 // ============================================================================
107 // Array Types
108 // ============================================================================
109
110 const numbers: number[] = [1, 2, 3, 4, 5]
111 const strings: Array<string> = ['a', 'b', 'c']
112 const users: User[] = [user, timestampedUser]
113
114 // Readonly arrays
115 const immutableNumbers: readonly number[] = [1, 2, 3]
116 // immutableNumbers.push(4) // Error: push does not exist on readonly array
117
118 // ============================================================================
119 // Tuple Types
120 // ============================================================================
121
122 type Point = [number, number]
123 type NamedPoint = [x: number, y: number, z?: number]
124
125 const point: Point = [10, 20]
126 const namedPoint: NamedPoint = [10, 20, 30]
127
128 // ============================================================================
129 // Function Types
130 // ============================================================================
131
132 // Function declaration
133 function add(a: number, b: number): number {
134 return a + b
135 }
136
137 // Arrow function
138 const subtract = (a: number, b: number): number => a - b
139
140 // Function type alias
141 type MathOperation = (a: number, b: number) => number
142
143 const multiply: MathOperation = (a, b) => a * b
144
145 // Optional parameters
146 function greet(name: string, greeting?: string): string {
147 return `${greeting ?? 'Hello'}, ${name}!`
148 }
149
150 // Default parameters
151 function createUser(name: string, role: string = 'user'): User {
152 return {
153 id: Math.random().toString(),
154 name,
155 email: `${name.toLowerCase()}@example.com`,
156 createdAt: new Date(),
157 }
158 }
159
160 // Rest parameters
161 function sum(...numbers: number[]): number {
162 return numbers.reduce((acc, n) => acc + n, 0)
163 }
164
165 // ============================================================================
166 // Type Inference
167 // ============================================================================
168
169 // Type is inferred as string
170 let inferredString = 'hello'
171
172 // Type is inferred as number
173 let inferredNumber = 42
174
175 // Type is inferred as { name: string; age: number }
176 let inferredObject = {
177 name: 'Alice',
178 age: 30,
179 }
180
181 // Return type is inferred as number
182 function inferredReturn(a: number, b: number) {
183 return a + b
184 }
185
186 // ============================================================================
187 // Type Narrowing
188 // ============================================================================
189
190 // typeof guard
191 function processValue(value: string | number) {
192 if (typeof value === 'string') {
193 // value is string here
194 return value.toUpperCase()
195 }
196 // value is number here
197 return value.toFixed(2)
198 }
199
200 // Truthiness narrowing
201 function printName(name: string | null | undefined) {
202 if (name) {
203 // name is string here
204 console.log(name.toUpperCase())
205 }
206 }
207
208 // Equality narrowing
209 function example(x: string | number, y: string | boolean) {
210 if (x === y) {
211 // x and y are both string here
212 console.log(x.toUpperCase(), y.toLowerCase())
213 }
214 }
215
216 // in operator narrowing
217 type Fish = { swim: () => void }
218 type Bird = { fly: () => void }
219
220 function move(animal: Fish | Bird) {
221 if ('swim' in animal) {
222 // animal is Fish here
223 animal.swim()
224 } else {
225 // animal is Bird here
226 animal.fly()
227 }
228 }
229
230 // instanceof narrowing
231 function processError(error: Error | string) {
232 if (error instanceof Error) {
233 // error is Error here
234 console.error(error.message)
235 } else {
236 // error is string here
237 console.error(error)
238 }
239 }
240
241 // ============================================================================
242 // Type Predicates (Custom Type Guards)
243 // ============================================================================
244
245 function isUser(value: unknown): value is User {
246 return (
247 typeof value === 'object' &&
248 value !== null &&
249 'id' in value &&
250 'name' in value &&
251 'email' in value
252 )
253 }
254
255 function processData(data: unknown) {
256 if (isUser(data)) {
257 // data is User here
258 console.log(data.name)
259 }
260 }
261
262 // ============================================================================
263 // Const Assertions
264 // ============================================================================
265
266 // Without const assertion
267 const mutableConfig = {
268 host: 'localhost',
269 port: 8080,
270 }
271 // mutableConfig.host = 'example.com' // OK
272
273 // With const assertion
274 const immutableConfig = {
275 host: 'localhost',
276 port: 8080,
277 } as const
278 // immutableConfig.host = 'example.com' // Error: cannot assign to readonly property
279
280 // Array with const assertion
281 const directions = ['north', 'south', 'east', 'west'] as const
282 // Type: readonly ["north", "south", "east", "west"]
283
284 // ============================================================================
285 // Literal Types
286 // ============================================================================
287
288 type Direction = 'north' | 'south' | 'east' | 'west'
289 type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
290 type DiceValue = 1 | 2 | 3 | 4 | 5 | 6
291
292 function move(direction: Direction, steps: number) {
293 console.log(`Moving ${direction} by ${steps} steps`)
294 }
295
296 move('north', 10) // OK
297 // move('up', 10) // Error: "up" is not assignable to Direction
298
299 // ============================================================================
300 // Index Signatures
301 // ============================================================================
302
303 interface StringMap {
304 [key: string]: string
305 }
306
307 const translations: StringMap = {
308 hello: 'Hola',
309 goodbye: 'Adiós',
310 thanks: 'Gracias',
311 }
312
313 // ============================================================================
314 // Utility Functions
315 // ============================================================================
316
317 // Type-safe object keys
318 function getObjectKeys<T extends object>(obj: T): Array<keyof T> {
319 return Object.keys(obj) as Array<keyof T>
320 }
321
322 // Type-safe property access
323 function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
324 return obj[key]
325 }
326
327 const userName = getProperty(user, 'name') // Type: string
328 const userAge = getProperty(user, 'age') // Type: number | undefined
329
330 // ============================================================================
331 // Named Return Values (Go-style)
332 // ============================================================================
333
334 function parseJSON(json: string): { data: unknown | null; err: Error | null } {
335 let data: unknown | null = null
336 let err: Error | null = null
337
338 try {
339 data = JSON.parse(json)
340 } catch (error) {
341 err = error instanceof Error ? error : new Error(String(error))
342 }
343
344 return { data, err }
345 }
346
347 // Usage
348 const { data, err } = parseJSON('{"name": "Alice"}')
349 if (err) {
350 console.error('Failed to parse JSON:', err.message)
351 } else {
352 console.log('Parsed data:', data)
353 }
354
355 // ============================================================================
356 // Exports
357 // ============================================================================
358
359 export type { User, Product, Status, ID, ApiResponse, TimestampedUser }
360 export { formatId, handleResponse, processValue, isUser, getProperty, parseJSON }
361
362