agentic-team-templates 0.13.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/package.json +1 -1
- package/src/index.js +91 -13
- package/src/index.test.js +95 -1
- package/templates/cpp-expert/.cursorrules/concurrency.md +211 -0
- package/templates/cpp-expert/.cursorrules/error-handling.md +170 -0
- package/templates/cpp-expert/.cursorrules/memory-and-ownership.md +220 -0
- package/templates/cpp-expert/.cursorrules/modern-cpp.md +211 -0
- package/templates/cpp-expert/.cursorrules/overview.md +87 -0
- package/templates/cpp-expert/.cursorrules/performance.md +223 -0
- package/templates/cpp-expert/.cursorrules/testing.md +230 -0
- package/templates/cpp-expert/.cursorrules/tooling.md +312 -0
- package/templates/cpp-expert/CLAUDE.md +242 -0
- package/templates/csharp-expert/.cursorrules/aspnet-core.md +311 -0
- package/templates/csharp-expert/.cursorrules/async-patterns.md +206 -0
- package/templates/csharp-expert/.cursorrules/dependency-injection.md +206 -0
- package/templates/csharp-expert/.cursorrules/error-handling.md +235 -0
- package/templates/csharp-expert/.cursorrules/language-features.md +204 -0
- package/templates/csharp-expert/.cursorrules/overview.md +92 -0
- package/templates/csharp-expert/.cursorrules/performance.md +251 -0
- package/templates/csharp-expert/.cursorrules/testing.md +282 -0
- package/templates/csharp-expert/.cursorrules/tooling.md +254 -0
- package/templates/csharp-expert/CLAUDE.md +360 -0
- package/templates/java-expert/.cursorrules/concurrency.md +209 -0
- package/templates/java-expert/.cursorrules/error-handling.md +205 -0
- package/templates/java-expert/.cursorrules/modern-java.md +216 -0
- package/templates/java-expert/.cursorrules/overview.md +81 -0
- package/templates/java-expert/.cursorrules/performance.md +239 -0
- package/templates/java-expert/.cursorrules/persistence.md +262 -0
- package/templates/java-expert/.cursorrules/spring-boot.md +262 -0
- package/templates/java-expert/.cursorrules/testing.md +272 -0
- package/templates/java-expert/.cursorrules/tooling.md +301 -0
- package/templates/java-expert/CLAUDE.md +325 -0
- package/templates/javascript-expert/.cursorrules/overview.md +5 -3
- package/templates/javascript-expert/.cursorrules/typescript-deep-dive.md +348 -0
- package/templates/javascript-expert/CLAUDE.md +34 -3
- package/templates/kotlin-expert/.cursorrules/coroutines.md +237 -0
- package/templates/kotlin-expert/.cursorrules/error-handling.md +149 -0
- package/templates/kotlin-expert/.cursorrules/frameworks.md +227 -0
- package/templates/kotlin-expert/.cursorrules/language-features.md +231 -0
- package/templates/kotlin-expert/.cursorrules/overview.md +77 -0
- package/templates/kotlin-expert/.cursorrules/performance.md +185 -0
- package/templates/kotlin-expert/.cursorrules/testing.md +213 -0
- package/templates/kotlin-expert/.cursorrules/tooling.md +258 -0
- package/templates/kotlin-expert/CLAUDE.md +276 -0
- package/templates/swift-expert/.cursorrules/concurrency.md +230 -0
- package/templates/swift-expert/.cursorrules/error-handling.md +213 -0
- package/templates/swift-expert/.cursorrules/language-features.md +246 -0
- package/templates/swift-expert/.cursorrules/overview.md +88 -0
- package/templates/swift-expert/.cursorrules/performance.md +260 -0
- package/templates/swift-expert/.cursorrules/swiftui.md +260 -0
- package/templates/swift-expert/.cursorrules/testing.md +286 -0
- package/templates/swift-expert/.cursorrules/tooling.md +285 -0
- package/templates/swift-expert/CLAUDE.md +275 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# TypeScript Deep Dive
|
|
2
|
+
|
|
3
|
+
Advanced type system mastery. Conditional types, mapped types, type-level programming, and runtime boundary validation.
|
|
4
|
+
|
|
5
|
+
This builds on the type safety foundations in overview.md. That file covers discriminated unions, branded types, and const assertions. This file goes deeper.
|
|
6
|
+
|
|
7
|
+
## Generics — Beyond the Basics
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// Constrained generics
|
|
11
|
+
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
|
|
12
|
+
return obj[key];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Generic with defaults
|
|
16
|
+
type Result<T, E = Error> =
|
|
17
|
+
| { ok: true; value: T }
|
|
18
|
+
| { ok: false; error: E };
|
|
19
|
+
|
|
20
|
+
// Multiple constraints
|
|
21
|
+
function merge<T extends object, U extends object>(a: T, b: U): T & U {
|
|
22
|
+
return { ...a, ...b };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Generic factory functions
|
|
26
|
+
function createStore<T>(initial: T) {
|
|
27
|
+
let state = initial;
|
|
28
|
+
return {
|
|
29
|
+
get: (): T => state,
|
|
30
|
+
set: (next: T): void => { state = next; },
|
|
31
|
+
update: (fn: (current: T) => T): void => { state = fn(state); },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
// Type is inferred from usage — no annotation needed at call site
|
|
35
|
+
const counter = createStore(0); // Store<number>
|
|
36
|
+
const users = createStore<User[]>([]); // Explicit when needed
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Conditional Types
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Basic conditional
|
|
43
|
+
type IsString<T> = T extends string ? true : false;
|
|
44
|
+
|
|
45
|
+
// Extract and Exclude (built-in, but understand how they work)
|
|
46
|
+
type Extract<T, U> = T extends U ? T : never;
|
|
47
|
+
type Exclude<T, U> = T extends U ? never : T;
|
|
48
|
+
|
|
49
|
+
// Practical: extract function return types from a module
|
|
50
|
+
type ApiMethods = {
|
|
51
|
+
getUser: (id: string) => Promise<User>;
|
|
52
|
+
createUser: (data: CreateUserInput) => Promise<User>;
|
|
53
|
+
deleteUser: (id: string) => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type ApiReturnTypes = {
|
|
57
|
+
[K in keyof ApiMethods]: ApiMethods[K] extends (...args: any[]) => infer R ? R : never;
|
|
58
|
+
};
|
|
59
|
+
// { getUser: Promise<User>; createUser: Promise<User>; deleteUser: Promise<void> }
|
|
60
|
+
|
|
61
|
+
// Unwrap Promise
|
|
62
|
+
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
|
|
63
|
+
type UserResult = Awaited<Promise<Promise<User>>>; // User
|
|
64
|
+
|
|
65
|
+
// Distributive conditional types — union members are distributed
|
|
66
|
+
type ToArray<T> = T extends unknown ? T[] : never;
|
|
67
|
+
type Result = ToArray<string | number>; // string[] | number[]
|
|
68
|
+
|
|
69
|
+
// Prevent distribution with tuple wrapping
|
|
70
|
+
type ToArrayNonDist<T> = [T] extends [unknown] ? T[] : never;
|
|
71
|
+
type Result2 = ToArrayNonDist<string | number>; // (string | number)[]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Mapped Types
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Transform all properties
|
|
78
|
+
type Readonly<T> = { readonly [K in keyof T]: T[K] };
|
|
79
|
+
type Partial<T> = { [K in keyof T]?: T[K] };
|
|
80
|
+
type Nullable<T> = { [K in keyof T]: T[K] | null };
|
|
81
|
+
|
|
82
|
+
// Key remapping (TypeScript 4.1+)
|
|
83
|
+
type Getters<T> = {
|
|
84
|
+
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
type UserGetters = Getters<{ name: string; age: number }>;
|
|
88
|
+
// { getName: () => string; getAge: () => number }
|
|
89
|
+
|
|
90
|
+
// Filter keys by value type
|
|
91
|
+
type PickByType<T, V> = {
|
|
92
|
+
[K in keyof T as T[K] extends V ? K : never]: T[K];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type StringFields = PickByType<User, string>;
|
|
96
|
+
// Only string-typed fields from User
|
|
97
|
+
|
|
98
|
+
// Deep mapped types
|
|
99
|
+
type DeepReadonly<T> = {
|
|
100
|
+
readonly [K in keyof T]: T[K] extends object
|
|
101
|
+
? T[K] extends Function
|
|
102
|
+
? T[K]
|
|
103
|
+
: DeepReadonly<T[K]>
|
|
104
|
+
: T[K];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
type DeepPartial<T> = {
|
|
108
|
+
[K in keyof T]?: T[K] extends object
|
|
109
|
+
? T[K] extends Function
|
|
110
|
+
? T[K]
|
|
111
|
+
: DeepPartial<T[K]>
|
|
112
|
+
: T[K];
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Template Literal Types
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
// Type-safe string manipulation
|
|
120
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
121
|
+
type ApiPath = `/api/v1/${string}`;
|
|
122
|
+
type Route = `${HttpMethod} ${ApiPath}`;
|
|
123
|
+
|
|
124
|
+
// Extract route parameters
|
|
125
|
+
type ExtractParams<T extends string> =
|
|
126
|
+
T extends `${string}:${infer Param}/${infer Rest}`
|
|
127
|
+
? Param | ExtractParams<`/${Rest}`>
|
|
128
|
+
: T extends `${string}:${infer Param}`
|
|
129
|
+
? Param
|
|
130
|
+
: never;
|
|
131
|
+
|
|
132
|
+
type Params = ExtractParams<'/users/:userId/posts/:postId'>;
|
|
133
|
+
// "userId" | "postId"
|
|
134
|
+
|
|
135
|
+
// Type-safe event emitter
|
|
136
|
+
type EventHandler<T extends string> = `on${Capitalize<T>}`;
|
|
137
|
+
type ClickHandler = EventHandler<'click'>; // "onClick"
|
|
138
|
+
|
|
139
|
+
// Dot-notation paths for nested objects
|
|
140
|
+
type DotPath<T, Prefix extends string = ''> = T extends object
|
|
141
|
+
? {
|
|
142
|
+
[K in keyof T & string]: T[K] extends object
|
|
143
|
+
? `${Prefix}${K}` | DotPath<T[K], `${Prefix}${K}.`>
|
|
144
|
+
: `${Prefix}${K}`;
|
|
145
|
+
}[keyof T & string]
|
|
146
|
+
: never;
|
|
147
|
+
|
|
148
|
+
type UserPaths = DotPath<{ name: string; address: { city: string; zip: string } }>;
|
|
149
|
+
// "name" | "address" | "address.city" | "address.zip"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Type Predicates and Assertion Functions
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Type predicates — custom narrowing functions
|
|
156
|
+
function isUser(value: unknown): value is User {
|
|
157
|
+
return (
|
|
158
|
+
typeof value === 'object' &&
|
|
159
|
+
value !== null &&
|
|
160
|
+
'id' in value &&
|
|
161
|
+
typeof (value as Record<string, unknown>).id === 'string' &&
|
|
162
|
+
'email' in value &&
|
|
163
|
+
typeof (value as Record<string, unknown>).email === 'string'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Works in filter — TypeScript narrows the array type
|
|
168
|
+
const users: User[] = mixedArray.filter(isUser);
|
|
169
|
+
|
|
170
|
+
// Assertion functions — throw or narrow
|
|
171
|
+
function assertUser(value: unknown): asserts value is User {
|
|
172
|
+
if (!isUser(value)) {
|
|
173
|
+
throw new TypeError('Expected User');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// After calling assertUser, TypeScript knows value is User
|
|
178
|
+
assertUser(data);
|
|
179
|
+
data.email; // OK — narrowed to User
|
|
180
|
+
|
|
181
|
+
// Type predicate for discriminated unions
|
|
182
|
+
function isSuccess<T>(result: Result<T>): result is { ok: true; value: T } {
|
|
183
|
+
return result.ok;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## satisfies Operator
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Validate a type without widening — the best of both worlds
|
|
191
|
+
const palette = {
|
|
192
|
+
red: [255, 0, 0],
|
|
193
|
+
green: '#00ff00',
|
|
194
|
+
blue: [0, 0, 255],
|
|
195
|
+
} satisfies Record<string, string | readonly number[]>;
|
|
196
|
+
|
|
197
|
+
// palette.red is number[] (not string | number[])
|
|
198
|
+
// palette.green is string (not string | number[])
|
|
199
|
+
// But TypeScript verified it matches Record<string, string | number[]>
|
|
200
|
+
|
|
201
|
+
// Route config example
|
|
202
|
+
const routes = {
|
|
203
|
+
home: { path: '/', component: HomePage },
|
|
204
|
+
about: { path: '/about', component: AboutPage },
|
|
205
|
+
user: { path: '/users/:id', component: UserPage },
|
|
206
|
+
} satisfies Record<string, { path: string; component: React.ComponentType }>;
|
|
207
|
+
|
|
208
|
+
// routes.home.path is the literal "/" — not widened to string
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Runtime Boundary Validation
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Types disappear at runtime. Validate at every I/O boundary.
|
|
215
|
+
import { z } from 'zod';
|
|
216
|
+
|
|
217
|
+
// Schema is the single source of truth — infer the type from it
|
|
218
|
+
const UserSchema = z.object({
|
|
219
|
+
id: z.string().uuid(),
|
|
220
|
+
name: z.string().min(1).max(200),
|
|
221
|
+
email: z.string().email(),
|
|
222
|
+
role: z.enum(['admin', 'user', 'viewer']),
|
|
223
|
+
createdAt: z.string().datetime(),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
type User = z.infer<typeof UserSchema>;
|
|
227
|
+
|
|
228
|
+
// Validate at boundaries
|
|
229
|
+
async function handleCreateUser(req: Request): Promise<Response> {
|
|
230
|
+
const body = await req.json();
|
|
231
|
+
const parsed = UserSchema.safeParse(body);
|
|
232
|
+
if (!parsed.success) {
|
|
233
|
+
return Response.json(
|
|
234
|
+
{ errors: parsed.error.flatten().fieldErrors },
|
|
235
|
+
{ status: 400 },
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
// parsed.data is fully typed User
|
|
239
|
+
const user = await db.users.create(parsed.data);
|
|
240
|
+
return Response.json(user, { status: 201 });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Validate environment variables at startup — fail fast
|
|
244
|
+
const EnvSchema = z.object({
|
|
245
|
+
DATABASE_URL: z.string().url(),
|
|
246
|
+
JWT_SECRET: z.string().min(32),
|
|
247
|
+
PORT: z.coerce.number().int().positive().default(3000),
|
|
248
|
+
});
|
|
249
|
+
export const env = EnvSchema.parse(process.env);
|
|
250
|
+
|
|
251
|
+
// Validate external API responses — don't trust the network
|
|
252
|
+
const ExternalUserSchema = z.object({
|
|
253
|
+
id: z.number(),
|
|
254
|
+
login: z.string(),
|
|
255
|
+
avatar_url: z.string().url(),
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
async function fetchGitHubUser(username: string) {
|
|
259
|
+
const response = await fetch(`https://api.github.com/users/${username}`);
|
|
260
|
+
const data = await response.json();
|
|
261
|
+
return ExternalUserSchema.parse(data); // Throws if shape is wrong
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Declaration Files and Library Types
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Augment third-party types
|
|
269
|
+
declare module 'express' {
|
|
270
|
+
interface Request {
|
|
271
|
+
user?: AuthenticatedUser;
|
|
272
|
+
requestId: string;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Augment global types
|
|
277
|
+
declare global {
|
|
278
|
+
interface Window {
|
|
279
|
+
__APP_CONFIG__: AppConfig;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Type-only imports (tree-shakeable, explicit intent)
|
|
284
|
+
import type { User } from './types.js';
|
|
285
|
+
|
|
286
|
+
// For library authors: ensure .d.ts files are generated
|
|
287
|
+
// tsconfig.json: "declaration": true, "declarationMap": true
|
|
288
|
+
// package.json: "types": "./dist/index.d.ts"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Utility Patterns
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// Make specific keys required
|
|
295
|
+
type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
296
|
+
type UserWithEmail = WithRequired<Partial<User>, 'email'>;
|
|
297
|
+
|
|
298
|
+
// Make specific keys optional
|
|
299
|
+
type WithOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
300
|
+
type UpdateUser = WithOptional<User, 'id' | 'createdAt'>;
|
|
301
|
+
|
|
302
|
+
// Strict extract for exhaustive checking
|
|
303
|
+
type StrictExtract<T, U extends T> = Extract<T, U>;
|
|
304
|
+
|
|
305
|
+
// Non-empty array type
|
|
306
|
+
type NonEmptyArray<T> = [T, ...T[]];
|
|
307
|
+
function first<T>(arr: NonEmptyArray<T>): T {
|
|
308
|
+
return arr[0]; // No undefined — guaranteed at least one element
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Exact types (prevent excess properties in specific contexts)
|
|
312
|
+
type Exact<T, Shape> = T extends Shape
|
|
313
|
+
? Exclude<keyof T, keyof Shape> extends never
|
|
314
|
+
? T
|
|
315
|
+
: never
|
|
316
|
+
: never;
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Anti-Patterns
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// Never: any
|
|
323
|
+
function process(data: any) { } // Type checker disabled entirely
|
|
324
|
+
// Use: unknown, then narrow with type guards or Zod
|
|
325
|
+
|
|
326
|
+
// Never: as for silencing errors
|
|
327
|
+
const user = data as User; // What if data is garbage?
|
|
328
|
+
// Use: runtime validation, then the type follows
|
|
329
|
+
|
|
330
|
+
// Never: enums (they generate runtime code and have surprising behavior)
|
|
331
|
+
enum Status { Active, Inactive } // 0, 1 — not "Active", "Inactive"
|
|
332
|
+
// Use: string literal unions
|
|
333
|
+
type Status = 'active' | 'inactive';
|
|
334
|
+
|
|
335
|
+
// Never: non-null assertion (!) without proof
|
|
336
|
+
user!.email; // Runtime NPE waiting to happen
|
|
337
|
+
// Use: if (user) { user.email }
|
|
338
|
+
|
|
339
|
+
// Never: @ts-ignore without explanation
|
|
340
|
+
// @ts-ignore
|
|
341
|
+
brokenCode();
|
|
342
|
+
// Use: @ts-expect-error — explanation of why this is necessary
|
|
343
|
+
// At minimum, @ts-expect-error will fail if the error is fixed (unlike @ts-ignore)
|
|
344
|
+
|
|
345
|
+
// Never: interface for unions or mapped types (use type alias)
|
|
346
|
+
// Interfaces are for object shapes that may be extended/implemented
|
|
347
|
+
// Type aliases are for unions, intersections, mapped types, conditional types
|
|
348
|
+
```
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
# JavaScript Expert Development Guide
|
|
1
|
+
# JavaScript & TypeScript Expert Development Guide
|
|
2
2
|
|
|
3
|
-
Comprehensive guidelines for principal-level JavaScript engineering across all runtimes, frameworks, and paradigms.
|
|
3
|
+
Comprehensive guidelines for principal-level JavaScript and TypeScript engineering across all runtimes, frameworks, and paradigms.
|
|
4
|
+
|
|
5
|
+
**This template covers both JavaScript and TypeScript.** TypeScript is the default — all code is written in strict TypeScript. Advanced type system patterns (conditional types, mapped types, generics, runtime validation) are included.
|
|
4
6
|
|
|
5
7
|
---
|
|
6
8
|
|
|
@@ -10,7 +12,7 @@ This guide applies to:
|
|
|
10
12
|
- Node.js services, CLIs, and tooling
|
|
11
13
|
- React, Vue, Angular, Svelte, and other UI frameworks
|
|
12
14
|
- Vanilla JavaScript and Web APIs
|
|
13
|
-
- TypeScript (strict mode, always)
|
|
15
|
+
- TypeScript (strict mode, always) — including advanced type system patterns
|
|
14
16
|
- Build tools, bundlers, and transpilers
|
|
15
17
|
- Testing at every level (unit, integration, E2E, performance)
|
|
16
18
|
|
|
@@ -76,6 +78,35 @@ const EVENTS = ['click', 'keydown', 'submit'] as const;
|
|
|
76
78
|
type EventName = (typeof EVENTS)[number];
|
|
77
79
|
```
|
|
78
80
|
|
|
81
|
+
### TypeScript Deep Dive
|
|
82
|
+
|
|
83
|
+
Advanced type system patterns beyond the basics:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Conditional types
|
|
87
|
+
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
|
|
88
|
+
|
|
89
|
+
// Mapped types with key remapping
|
|
90
|
+
type Getters<T> = {
|
|
91
|
+
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Template literal type extraction
|
|
95
|
+
type ExtractParams<T extends string> =
|
|
96
|
+
T extends `${string}:${infer Param}/${infer Rest}`
|
|
97
|
+
? Param | ExtractParams<`/${Rest}`>
|
|
98
|
+
: T extends `${string}:${infer Param}` ? Param : never;
|
|
99
|
+
|
|
100
|
+
// satisfies — validate without widening
|
|
101
|
+
const config = { port: 3000, host: 'localhost' } satisfies Record<string, string | number>;
|
|
102
|
+
// config.port is still number, not string | number
|
|
103
|
+
|
|
104
|
+
// Runtime boundary validation with Zod
|
|
105
|
+
const UserSchema = z.object({ id: z.string().uuid(), email: z.string().email() });
|
|
106
|
+
type User = z.infer<typeof UserSchema>;
|
|
107
|
+
// Types disappear at runtime — validate at every I/O boundary
|
|
108
|
+
```
|
|
109
|
+
|
|
79
110
|
### Functional Patterns
|
|
80
111
|
|
|
81
112
|
```typescript
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Kotlin Coroutines
|
|
2
|
+
|
|
3
|
+
Structured concurrency is the law. Every coroutine has a scope. Every scope has a lifecycle.
|
|
4
|
+
|
|
5
|
+
## Fundamentals
|
|
6
|
+
|
|
7
|
+
```kotlin
|
|
8
|
+
// Coroutines are lightweight — thousands are cheap
|
|
9
|
+
suspend fun fetchUser(id: UserId): User {
|
|
10
|
+
return userRepository.findById(id)
|
|
11
|
+
?: throw NotFoundException("User", id.value)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// suspend functions can only be called from coroutines or other suspend functions
|
|
15
|
+
// They mark suspension points — where the function may pause and resume
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Structured Concurrency
|
|
19
|
+
|
|
20
|
+
```kotlin
|
|
21
|
+
// Every coroutine lives in a scope — when the scope cancels, all children cancel
|
|
22
|
+
class UserService(
|
|
23
|
+
private val userRepo: UserRepository,
|
|
24
|
+
private val emailService: EmailService,
|
|
25
|
+
) {
|
|
26
|
+
// CoroutineScope tied to service lifecycle
|
|
27
|
+
suspend fun register(request: CreateUserRequest): User {
|
|
28
|
+
val user = userRepo.create(request)
|
|
29
|
+
// This child coroutine is tied to the caller's scope
|
|
30
|
+
emailService.sendWelcome(user.email)
|
|
31
|
+
return user
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Parallel decomposition with coroutineScope
|
|
36
|
+
suspend fun loadDashboard(userId: UserId): Dashboard = coroutineScope {
|
|
37
|
+
val userDeferred = async { userService.findById(userId) }
|
|
38
|
+
val ordersDeferred = async { orderService.findRecent(userId) }
|
|
39
|
+
val statsDeferred = async { statsService.forUser(userId) }
|
|
40
|
+
|
|
41
|
+
Dashboard(
|
|
42
|
+
user = userDeferred.await(),
|
|
43
|
+
orders = ordersDeferred.await(),
|
|
44
|
+
stats = statsDeferred.await()
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
// If any async fails, ALL are cancelled — no orphaned work
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Dispatchers
|
|
51
|
+
|
|
52
|
+
```kotlin
|
|
53
|
+
// Dispatchers.IO — blocking I/O (database, file, network)
|
|
54
|
+
withContext(Dispatchers.IO) {
|
|
55
|
+
database.query(sql)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Dispatchers.Default — CPU-intensive computation
|
|
59
|
+
withContext(Dispatchers.Default) {
|
|
60
|
+
data.map { expensiveTransform(it) }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Dispatchers.Main — UI thread (Android)
|
|
64
|
+
withContext(Dispatchers.Main) {
|
|
65
|
+
updateUI(result)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Never create custom thread pools unless you have a specific reason
|
|
69
|
+
// The built-in dispatchers are well-tuned
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Flow
|
|
73
|
+
|
|
74
|
+
```kotlin
|
|
75
|
+
// Cold asynchronous stream — values emitted on demand
|
|
76
|
+
fun observeUsers(): Flow<List<User>> = flow {
|
|
77
|
+
while (true) {
|
|
78
|
+
emit(userRepository.findAll())
|
|
79
|
+
delay(5.seconds)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Operators
|
|
84
|
+
val activeUserNames: Flow<String> = observeUsers()
|
|
85
|
+
.map { users -> users.filter { it.isActive } }
|
|
86
|
+
.flatMapConcat { users -> users.asFlow() }
|
|
87
|
+
.map { it.name }
|
|
88
|
+
.distinctUntilChanged()
|
|
89
|
+
|
|
90
|
+
// StateFlow — hot observable with current value (replaces LiveData)
|
|
91
|
+
class UserViewModel : ViewModel() {
|
|
92
|
+
private val _state = MutableStateFlow<UiState>(UiState.Loading)
|
|
93
|
+
val state: StateFlow<UiState> = _state.asStateFlow()
|
|
94
|
+
|
|
95
|
+
fun loadUser(id: UserId) {
|
|
96
|
+
viewModelScope.launch {
|
|
97
|
+
_state.value = UiState.Loading
|
|
98
|
+
try {
|
|
99
|
+
val user = userService.findById(id)
|
|
100
|
+
_state.value = UiState.Success(user)
|
|
101
|
+
} catch (e: Exception) {
|
|
102
|
+
_state.value = UiState.Error(e.message ?: "Unknown error")
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// SharedFlow — hot stream for events (no replay by default)
|
|
109
|
+
class EventBus {
|
|
110
|
+
private val _events = MutableSharedFlow<AppEvent>()
|
|
111
|
+
val events: SharedFlow<AppEvent> = _events.asSharedFlow()
|
|
112
|
+
|
|
113
|
+
suspend fun emit(event: AppEvent) = _events.emit(event)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Collecting flows
|
|
117
|
+
lifecycleScope.launch {
|
|
118
|
+
viewModel.state.collect { state ->
|
|
119
|
+
when (state) {
|
|
120
|
+
is UiState.Loading -> showLoading()
|
|
121
|
+
is UiState.Success -> showUser(state.user)
|
|
122
|
+
is UiState.Error -> showError(state.message)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Cancellation
|
|
129
|
+
|
|
130
|
+
```kotlin
|
|
131
|
+
// Cooperative cancellation — coroutines must check for cancellation
|
|
132
|
+
suspend fun processItems(items: List<Item>) {
|
|
133
|
+
for (item in items) {
|
|
134
|
+
ensureActive() // Throws CancellationException if cancelled
|
|
135
|
+
process(item)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// All suspend functions in kotlinx.coroutines are cancellable
|
|
140
|
+
// delay(), yield(), withContext(), etc. check for cancellation
|
|
141
|
+
|
|
142
|
+
// CancellationException is special — it doesn't propagate as failure
|
|
143
|
+
// Use it for normal cancellation, not for errors
|
|
144
|
+
|
|
145
|
+
// Timeout
|
|
146
|
+
val result = withTimeout(5.seconds) {
|
|
147
|
+
fetchDataFromNetwork()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Timeout with null instead of exception
|
|
151
|
+
val result = withTimeoutOrNull(5.seconds) {
|
|
152
|
+
fetchDataFromNetwork()
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Exception Handling
|
|
157
|
+
|
|
158
|
+
```kotlin
|
|
159
|
+
// CoroutineExceptionHandler — last resort for uncaught exceptions
|
|
160
|
+
val handler = CoroutineExceptionHandler { _, exception ->
|
|
161
|
+
logger.error("Uncaught coroutine exception", exception)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// supervisorScope — child failure doesn't cancel siblings
|
|
165
|
+
suspend fun fetchAllData(): DashboardData = supervisorScope {
|
|
166
|
+
val user = async { fetchUser() }
|
|
167
|
+
val orders = async { fetchOrders() } // If this fails...
|
|
168
|
+
val stats = async { fetchStats() } // ...this continues
|
|
169
|
+
|
|
170
|
+
DashboardData(
|
|
171
|
+
user = user.await(),
|
|
172
|
+
orders = runCatching { orders.await() }.getOrDefault(emptyList()),
|
|
173
|
+
stats = runCatching { stats.await() }.getOrNull()
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// try-catch in coroutines
|
|
178
|
+
launch {
|
|
179
|
+
try {
|
|
180
|
+
riskyOperation()
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
if (e is CancellationException) throw e // Never catch cancellation
|
|
183
|
+
logger.error("Operation failed", e)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Channels
|
|
189
|
+
|
|
190
|
+
```kotlin
|
|
191
|
+
// Producer-consumer with bounded channel
|
|
192
|
+
val channel = Channel<Event>(capacity = Channel.BUFFERED)
|
|
193
|
+
|
|
194
|
+
// Producer
|
|
195
|
+
launch {
|
|
196
|
+
for (event in events) {
|
|
197
|
+
channel.send(event) // Suspends if buffer is full
|
|
198
|
+
}
|
|
199
|
+
channel.close()
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Consumer
|
|
203
|
+
launch {
|
|
204
|
+
for (event in channel) { // Iterates until channel is closed
|
|
205
|
+
process(event)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Fan-out: multiple consumers from one channel
|
|
210
|
+
repeat(workerCount) {
|
|
211
|
+
launch { for (event in channel) process(event) }
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Anti-Patterns
|
|
216
|
+
|
|
217
|
+
```kotlin
|
|
218
|
+
// Never: GlobalScope
|
|
219
|
+
GlobalScope.launch { } // No lifecycle, no cancellation, leaks coroutines
|
|
220
|
+
// Use: viewModelScope, lifecycleScope, or a custom CoroutineScope
|
|
221
|
+
|
|
222
|
+
// Never: runBlocking in production code (except main() or tests)
|
|
223
|
+
runBlocking { fetchData() } // Blocks the thread, defeats the purpose
|
|
224
|
+
// Use: suspend functions all the way up
|
|
225
|
+
|
|
226
|
+
// Never: catching CancellationException without rethrowing
|
|
227
|
+
catch (e: Exception) { log(e) } // Swallows cancellation!
|
|
228
|
+
// Use: catch (e: Exception) { if (e is CancellationException) throw e; log(e) }
|
|
229
|
+
|
|
230
|
+
// Never: launch without error handling
|
|
231
|
+
launch { riskyWork() } // Exception silently crashes
|
|
232
|
+
// Use: launch with try-catch or CoroutineExceptionHandler
|
|
233
|
+
|
|
234
|
+
// Never: Thread.sleep() in coroutines
|
|
235
|
+
Thread.sleep(1000) // Blocks the thread
|
|
236
|
+
// Use: delay(1000) // Suspends without blocking
|
|
237
|
+
```
|