@grimoire-cc/cli 0.4.1 → 0.5.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.
@@ -0,0 +1,389 @@
1
+ # Type System Patterns
2
+
3
+ ## Table of Contents
4
+
5
+ - [Generics Best Practices](#generics-best-practices)
6
+ - [Conditional Types](#conditional-types)
7
+ - [Mapped Types](#mapped-types)
8
+ - [Template Literal Types](#template-literal-types)
9
+ - [Type Guards and Narrowing](#type-guards-and-narrowing)
10
+ - [Utility Type Recipes](#utility-type-recipes)
11
+ - [Recursive Types](#recursive-types)
12
+
13
+ ## Generics Best Practices
14
+
15
+ ### Constrain Early, Infer Late
16
+
17
+ ```typescript
18
+ // Bad — too loose
19
+ function getProperty<T>(obj: T, key: string): unknown {
20
+ return (obj as Record<string, unknown>)[key];
21
+ }
22
+
23
+ // Good — constrained and type-safe
24
+ function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
25
+ return obj[key];
26
+ }
27
+ ```
28
+
29
+ ### Name Generic Parameters Meaningfully
30
+
31
+ ```typescript
32
+ // Bad — single-letter overload
33
+ function transform<T, U, V>(input: T, fn: (x: U) => V): V { /* ... */ }
34
+
35
+ // Good — descriptive names for complex generics
36
+ function transform<TInput, TIntermediate, TOutput>(
37
+ input: TInput,
38
+ fn: (x: TIntermediate) => TOutput,
39
+ ): TOutput { /* ... */ }
40
+
41
+ // Single-letter is fine for simple, well-understood generics
42
+ function identity<T>(value: T): T { return value; }
43
+ ```
44
+
45
+ ### Use `const` Type Parameters for Literal Inference
46
+
47
+ ```typescript
48
+ function createConfig<const T extends Record<string, unknown>>(config: T): T {
49
+ return config;
50
+ }
51
+
52
+ // Infers { readonly host: "localhost"; readonly port: 3000 }
53
+ const cfg = createConfig({ host: "localhost", port: 3000 });
54
+ ```
55
+
56
+ ### Default Type Parameters
57
+
58
+ ```typescript
59
+ interface EventEmitter<TEvents extends Record<string, unknown[]> = Record<string, unknown[]>> {
60
+ on<K extends keyof TEvents>(event: K, handler: (...args: TEvents[K]) => void): void;
61
+ emit<K extends keyof TEvents>(event: K, ...args: TEvents[K]): void;
62
+ }
63
+
64
+ // Works without explicit type argument
65
+ const emitter: EventEmitter = createEmitter();
66
+
67
+ // Or with specific events
68
+ interface AppEvents {
69
+ login: [userId: string];
70
+ error: [code: number, message: string];
71
+ }
72
+ const app: EventEmitter<AppEvents> = createEmitter();
73
+ ```
74
+
75
+ ### Avoid Over-Generification
76
+
77
+ ```typescript
78
+ // Bad — generic adds no value here
79
+ function add<T extends number>(a: T, b: T): number {
80
+ return a + b;
81
+ }
82
+
83
+ // Good — simple parameter types when generics don't help
84
+ function add(a: number, b: number): number {
85
+ return a + b;
86
+ }
87
+ ```
88
+
89
+ ## Conditional Types
90
+
91
+ ### Basic Pattern
92
+
93
+ ```typescript
94
+ type IsString<T> = T extends string ? true : false;
95
+
96
+ type A = IsString<"hello">; // true
97
+ type B = IsString<42>; // false
98
+ ```
99
+
100
+ ### Extracting from Unions
101
+
102
+ ```typescript
103
+ // Extract only string members from a union
104
+ type StringMembers<T> = T extends string ? T : never;
105
+
106
+ type Mixed = "hello" | 42 | "world" | true;
107
+ type OnlyStrings = StringMembers<Mixed>; // "hello" | "world"
108
+ ```
109
+
110
+ ### `infer` for Type Extraction
111
+
112
+ ```typescript
113
+ // Extract return type of a function
114
+ type ReturnOf<T> = T extends (...args: unknown[]) => infer R ? R : never;
115
+
116
+ // Extract element type from array
117
+ type ElementOf<T> = T extends readonly (infer E)[] ? E : never;
118
+
119
+ // Extract promise value
120
+ type Awaited<T> = T extends Promise<infer V> ? Awaited<V> : T;
121
+
122
+ // Extract specific tuple positions
123
+ type First<T> = T extends [infer F, ...unknown[]] ? F : never;
124
+ type Last<T> = T extends [...unknown[], infer L] ? L : never;
125
+ ```
126
+
127
+ ### Distributive vs. Non-Distributive
128
+
129
+ ```typescript
130
+ // Distributive — applies to each union member separately
131
+ type ToArray<T> = T extends unknown ? T[] : never;
132
+ type Result = ToArray<string | number>; // string[] | number[]
133
+
134
+ // Non-distributive — wrapping in tuple prevents distribution
135
+ type ToArrayND<T> = [T] extends [unknown] ? T[] : never;
136
+ type Result2 = ToArrayND<string | number>; // (string | number)[]
137
+ ```
138
+
139
+ ## Mapped Types
140
+
141
+ ### Basic Transformation
142
+
143
+ ```typescript
144
+ // Make all properties optional
145
+ type Optional<T> = { [K in keyof T]?: T[K] };
146
+
147
+ // Make all properties required
148
+ type Required<T> = { [K in keyof T]-?: T[K] };
149
+
150
+ // Make all properties readonly
151
+ type Immutable<T> = { readonly [K in keyof T]: T[K] };
152
+ ```
153
+
154
+ ### Key Remapping with `as`
155
+
156
+ ```typescript
157
+ // Prefix all keys
158
+ type Prefixed<T, P extends string> = {
159
+ [K in keyof T as `${P}${string & K}`]: T[K];
160
+ };
161
+
162
+ interface User { name: string; age: number }
163
+ type PrefixedUser = Prefixed<User, "user_">;
164
+ // { user_name: string; user_age: number }
165
+ ```
166
+
167
+ ### Filtering Properties
168
+
169
+ ```typescript
170
+ // Keep only string-valued properties
171
+ type StringProps<T> = {
172
+ [K in keyof T as T[K] extends string ? K : never]: T[K];
173
+ };
174
+
175
+ interface Mixed { name: string; age: number; email: string }
176
+ type OnlyStrings = StringProps<Mixed>;
177
+ // { name: string; email: string }
178
+ ```
179
+
180
+ ### Deep Readonly
181
+
182
+ ```typescript
183
+ type DeepReadonly<T> = T extends Function
184
+ ? T
185
+ : T extends object
186
+ ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
187
+ : T;
188
+ ```
189
+
190
+ ## Template Literal Types
191
+
192
+ ### Event System
193
+
194
+ ```typescript
195
+ type EventName = "click" | "hover" | "focus";
196
+ type HandlerName = `on${Capitalize<EventName>}`;
197
+ // "onClick" | "onHover" | "onFocus"
198
+
199
+ type EventHandlers = {
200
+ [K in EventName as `on${Capitalize<K>}`]: (event: Event) => void;
201
+ };
202
+ ```
203
+
204
+ ### Route Parameters
205
+
206
+ ```typescript
207
+ type ExtractParams<T extends string> =
208
+ T extends `${string}:${infer Param}/${infer Rest}`
209
+ ? { [K in Param | keyof ExtractParams<Rest>]: string }
210
+ : T extends `${string}:${infer Param}`
211
+ ? { [K in Param]: string }
212
+ : Record<string, never>;
213
+
214
+ type Params = ExtractParams<"/users/:userId/posts/:postId">;
215
+ // { userId: string; postId: string }
216
+ ```
217
+
218
+ ### String Manipulation Types
219
+
220
+ ```typescript
221
+ // Built-in intrinsic types
222
+ type Upper = Uppercase<"hello">; // "HELLO"
223
+ type Lower = Lowercase<"HELLO">; // "hello"
224
+ type Cap = Capitalize<"hello">; // "Hello"
225
+ type Uncap = Uncapitalize<"Hello">; // "hello"
226
+
227
+ // Combine for conventions
228
+ type CamelToSnake<S extends string> =
229
+ S extends `${infer Head}${infer Tail}`
230
+ ? Tail extends Uncapitalize<Tail>
231
+ ? `${Lowercase<Head>}${CamelToSnake<Tail>}`
232
+ : `${Lowercase<Head>}_${CamelToSnake<Tail>}`
233
+ : S;
234
+ ```
235
+
236
+ ## Type Guards and Narrowing
237
+
238
+ ### Custom Type Guards
239
+
240
+ ```typescript
241
+ // Type predicate — explicitly declares narrowing
242
+ function isUser(value: unknown): value is User {
243
+ return (
244
+ typeof value === "object" &&
245
+ value !== null &&
246
+ "id" in value &&
247
+ "name" in value
248
+ );
249
+ }
250
+
251
+ // Assertion function — throws or narrows
252
+ function assertUser(value: unknown): asserts value is User {
253
+ if (!isUser(value)) {
254
+ throw new TypeError("Expected User");
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Inferred Type Predicates (TS 5.5+)
260
+
261
+ ```typescript
262
+ // TypeScript now infers the type predicate automatically
263
+ const users = items.filter((item) => item.type === "user");
264
+ // Inferred as User[] — no explicit type guard needed
265
+
266
+ // Also works with nullish filtering
267
+ const nonNull = items.filter((item) => item != null);
268
+ // Inferred as NonNullable<T>[]
269
+ ```
270
+
271
+ ### Narrowing Patterns
272
+
273
+ ```typescript
274
+ // `in` operator narrows
275
+ function handle(shape: Circle | Square) {
276
+ if ("radius" in shape) {
277
+ // shape is Circle
278
+ }
279
+ }
280
+
281
+ // typeof narrows
282
+ function process(value: string | number) {
283
+ if (typeof value === "string") {
284
+ // value is string
285
+ }
286
+ }
287
+
288
+ // instanceof narrows
289
+ function handleError(error: unknown) {
290
+ if (error instanceof TypeError) {
291
+ // error is TypeError
292
+ }
293
+ }
294
+
295
+ // Discriminant property narrows
296
+ function handleResult(result: Result<User>) {
297
+ if (result.ok) {
298
+ // result is { ok: true; value: User }
299
+ }
300
+ }
301
+ ```
302
+
303
+ ## Utility Type Recipes
304
+
305
+ ### Pick and Omit Variants
306
+
307
+ ```typescript
308
+ // Pick only required properties
309
+ type RequiredKeys<T> = {
310
+ [K in keyof T]-?: undefined extends T[K] ? never : K;
311
+ }[keyof T];
312
+
313
+ type RequiredPick<T> = Pick<T, RequiredKeys<T>>;
314
+
315
+ // Make specific properties optional
316
+ type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
317
+
318
+ // Make specific properties required
319
+ type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
320
+ ```
321
+
322
+ ### Strict Omit
323
+
324
+ ```typescript
325
+ // Built-in Omit doesn't check keys — this one does
326
+ type StrictOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
327
+ ```
328
+
329
+ ### Union to Intersection
330
+
331
+ ```typescript
332
+ type UnionToIntersection<U> =
333
+ (U extends unknown ? (arg: U) => void : never) extends (arg: infer I) => void
334
+ ? I
335
+ : never;
336
+ ```
337
+
338
+ ### Merge Types
339
+
340
+ ```typescript
341
+ // Like intersection but shows resolved keys in IDE
342
+ type Merge<A, B> = {
343
+ [K in keyof A | keyof B]: K extends keyof B
344
+ ? B[K]
345
+ : K extends keyof A
346
+ ? A[K]
347
+ : never;
348
+ };
349
+ ```
350
+
351
+ ## Recursive Types
352
+
353
+ ### Deep Partial
354
+
355
+ ```typescript
356
+ type DeepPartial<T> = T extends object
357
+ ? { [K in keyof T]?: DeepPartial<T[K]> }
358
+ : T;
359
+ ```
360
+
361
+ ### JSON Type
362
+
363
+ ```typescript
364
+ type JsonValue =
365
+ | string
366
+ | number
367
+ | boolean
368
+ | null
369
+ | JsonValue[]
370
+ | { [key: string]: JsonValue };
371
+ ```
372
+
373
+ ### Nested Path Types
374
+
375
+ ```typescript
376
+ type Path<T, K extends keyof T = keyof T> = K extends string
377
+ ? T[K] extends object
378
+ ? K | `${K}.${Path<T[K]>}`
379
+ : K
380
+ : never;
381
+
382
+ interface Config {
383
+ db: { host: string; port: number };
384
+ app: { name: string };
385
+ }
386
+
387
+ type ConfigPaths = Path<Config>;
388
+ // "db" | "db.host" | "db.port" | "app" | "app.name"
389
+ ```