@biggora/claude-plugins 1.2.2 → 1.3.1

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.
Files changed (85) hide show
  1. package/README.md +5 -1
  2. package/package.json +1 -1
  3. package/registry/registry.json +15 -0
  4. package/specs/coding.md +11 -0
  5. package/src/commands/skills/add.js +115 -31
  6. package/src/commands/skills/list.js +25 -52
  7. package/src/commands/skills/remove.js +45 -27
  8. package/src/commands/skills/resolve.js +104 -0
  9. package/src/commands/skills/update.js +58 -74
  10. package/src/config.js +5 -0
  11. package/src/skills/nest-best-practices/SKILL.md +251 -0
  12. package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
  13. package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
  14. package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
  15. package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
  16. package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
  17. package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
  18. package/src/skills/nest-best-practices/references/core-modules.md +138 -0
  19. package/src/skills/nest-best-practices/references/core-providers.md +188 -0
  20. package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
  21. package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
  22. package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
  23. package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
  24. package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
  25. package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
  26. package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
  27. package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
  28. package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
  29. package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
  30. package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
  31. package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
  32. package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
  33. package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
  34. package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
  35. package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
  36. package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
  37. package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
  38. package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
  39. package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
  40. package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
  41. package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
  42. package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
  43. package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
  44. package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
  45. package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
  46. package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
  47. package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
  48. package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
  49. package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
  50. package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
  51. package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
  52. package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
  53. package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
  54. package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
  55. package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
  56. package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
  57. package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
  58. package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
  59. package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
  60. package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
  61. package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
  62. package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
  63. package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
  64. package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
  65. package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
  66. package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
  67. package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
  68. package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
  69. package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
  70. package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
  71. package/src/skills/typescript-expert/SKILL.md +145 -0
  72. package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
  73. package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
  74. package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
  75. package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
  76. package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
  77. package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
  78. package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
  79. package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
  80. package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
  81. package/src/skills/typescript-expert/references/core-generics.md +246 -0
  82. package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
  83. package/src/skills/typescript-expert/references/core-type-system.md +261 -0
  84. package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
  85. package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
@@ -0,0 +1,308 @@
1
+ # Type Guards and Discriminated Unions
2
+
3
+ Type guards let you narrow a type within a conditional block. Discriminated unions combine literal types with exhaustive checking for robust state modeling.
4
+
5
+ ## Built-in Narrowing
6
+
7
+ TypeScript narrows types automatically in many control flow patterns:
8
+
9
+ ```typescript
10
+ // typeof
11
+ function process(x: string | number) {
12
+ if (typeof x === "string") {
13
+ x.toUpperCase(); // x is string
14
+ }
15
+ }
16
+
17
+ // instanceof
18
+ function handle(err: Error | string) {
19
+ if (err instanceof Error) {
20
+ err.message; // err is Error
21
+ }
22
+ }
23
+
24
+ // in operator
25
+ function move(animal: { swim?: () => void; fly?: () => void }) {
26
+ if ("swim" in animal) {
27
+ animal.swim!();
28
+ }
29
+ }
30
+
31
+ // Truthiness
32
+ function print(name: string | null | undefined) {
33
+ if (name) {
34
+ name.toUpperCase(); // string (null and undefined removed)
35
+ }
36
+ }
37
+
38
+ // Equality
39
+ function compare(a: string | number, b: string | boolean) {
40
+ if (a === b) {
41
+ a; // string (only common type)
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## User-Defined Type Guards
47
+
48
+ When built-in narrowing isn't enough, write custom type guard functions:
49
+
50
+ ```typescript
51
+ // Type predicate — return type is `paramName is Type`
52
+ function isString(value: unknown): value is string {
53
+ return typeof value === "string";
54
+ }
55
+
56
+ function process(value: unknown) {
57
+ if (isString(value)) {
58
+ value.toUpperCase(); // TypeScript knows value is string
59
+ }
60
+ }
61
+
62
+ // Checking object shapes
63
+ interface User { type: "user"; name: string; email: string }
64
+ interface Admin { type: "admin"; name: string; permissions: string[] }
65
+
66
+ function isAdmin(person: User | Admin): person is Admin {
67
+ return person.type === "admin";
68
+ }
69
+
70
+ // Array filtering with type guards
71
+ const items: (string | null)[] = ["hello", null, "world", null];
72
+ const strings: string[] = items.filter((x): x is string => x !== null);
73
+ ```
74
+
75
+ ### Inferred Type Predicates (TS 5.5+)
76
+
77
+ TypeScript 5.5 can infer type predicates for simple arrow functions:
78
+
79
+ ```typescript
80
+ // Before 5.5: needed explicit annotation
81
+ const strings = items.filter((x): x is string => x !== null);
82
+
83
+ // TS 5.5+: inferred automatically
84
+ const strings = items.filter(x => x !== null); // string[]
85
+
86
+ // Also works with Boolean
87
+ const truthy = items.filter(Boolean); // string[] (nulls removed)
88
+ ```
89
+
90
+ ## Assertion Functions
91
+
92
+ Assertion functions narrow the type for all subsequent code (not just the if-block):
93
+
94
+ ```typescript
95
+ function assertIsString(value: unknown): asserts value is string {
96
+ if (typeof value !== "string") {
97
+ throw new Error(`Expected string, got ${typeof value}`);
98
+ }
99
+ }
100
+
101
+ function process(value: unknown) {
102
+ assertIsString(value);
103
+ // From here on, value is string
104
+ value.toUpperCase();
105
+ }
106
+
107
+ // Assert non-null
108
+ function assertDefined<T>(value: T): asserts value is NonNullable<T> {
109
+ if (value == null) {
110
+ throw new Error("Value must be defined");
111
+ }
112
+ }
113
+ ```
114
+
115
+ ## Discriminated Unions
116
+
117
+ A discriminated union is a union where each member has a literal property (the "discriminant") that uniquely identifies it:
118
+
119
+ ```typescript
120
+ interface Circle {
121
+ kind: "circle";
122
+ radius: number;
123
+ }
124
+ interface Rectangle {
125
+ kind: "rectangle";
126
+ width: number;
127
+ height: number;
128
+ }
129
+ interface Triangle {
130
+ kind: "triangle";
131
+ base: number;
132
+ height: number;
133
+ }
134
+
135
+ type Shape = Circle | Rectangle | Triangle;
136
+
137
+ function area(shape: Shape): number {
138
+ switch (shape.kind) {
139
+ case "circle":
140
+ return Math.PI * shape.radius ** 2;
141
+ case "rectangle":
142
+ return shape.width * shape.height;
143
+ case "triangle":
144
+ return 0.5 * shape.base * shape.height;
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Exhaustive Checking
150
+
151
+ Ensure all union members are handled:
152
+
153
+ ```typescript
154
+ // Method 1: never in default
155
+ function area(shape: Shape): number {
156
+ switch (shape.kind) {
157
+ case "circle":
158
+ return Math.PI * shape.radius ** 2;
159
+ case "rectangle":
160
+ return shape.width * shape.height;
161
+ case "triangle":
162
+ return 0.5 * shape.base * shape.height;
163
+ default:
164
+ // If Shape gains a new member, this line errors
165
+ const _exhaustive: never = shape;
166
+ return _exhaustive;
167
+ }
168
+ }
169
+
170
+ // Method 2: Helper function
171
+ function assertNever(x: never): never {
172
+ throw new Error(`Unexpected value: ${x}`);
173
+ }
174
+
175
+ // Method 3: satisfies never
176
+ function area(shape: Shape): number {
177
+ switch (shape.kind) {
178
+ case "circle": return Math.PI * shape.radius ** 2;
179
+ case "rectangle": return shape.width * shape.height;
180
+ case "triangle": return 0.5 * shape.base * shape.height;
181
+ default: return shape satisfies never;
182
+ }
183
+ }
184
+ ```
185
+
186
+ ### Modeling Application State
187
+
188
+ ```typescript
189
+ type RequestState<T> =
190
+ | { status: "idle" }
191
+ | { status: "loading" }
192
+ | { status: "success"; data: T }
193
+ | { status: "error"; error: Error };
194
+
195
+ function renderUser(state: RequestState<User>) {
196
+ switch (state.status) {
197
+ case "idle":
198
+ return "Click to load";
199
+ case "loading":
200
+ return "Loading...";
201
+ case "success":
202
+ return `Hello, ${state.data.name}`; // data is available
203
+ case "error":
204
+ return `Error: ${state.error.message}`; // error is available
205
+ }
206
+ }
207
+ ```
208
+
209
+ ### Result Type Pattern
210
+
211
+ ```typescript
212
+ type Result<T, E = Error> =
213
+ | { ok: true; value: T }
214
+ | { ok: false; error: E };
215
+
216
+ function divide(a: number, b: number): Result<number, string> {
217
+ if (b === 0) return { ok: false, error: "Division by zero" };
218
+ return { ok: true, value: a / b };
219
+ }
220
+
221
+ const result = divide(10, 3);
222
+ if (result.ok) {
223
+ console.log(result.value); // number
224
+ } else {
225
+ console.log(result.error); // string
226
+ }
227
+ ```
228
+
229
+ ## Narrowing with Control Flow
230
+
231
+ TypeScript tracks narrowing through assignments, returns, and throws:
232
+
233
+ ```typescript
234
+ function process(value: string | null) {
235
+ if (value === null) {
236
+ return; // Early return narrows the rest
237
+ }
238
+ // value is string from here
239
+ value.toUpperCase();
240
+ }
241
+
242
+ function validate(value: unknown): string {
243
+ if (typeof value !== "string") {
244
+ throw new Error("Not a string");
245
+ }
246
+ // value is string from here
247
+ return value.trim();
248
+ }
249
+
250
+ // Assignment narrowing
251
+ let x: string | number;
252
+ x = "hello";
253
+ x.toUpperCase(); // OK — x is string after assignment
254
+ x = 42;
255
+ x.toFixed(2); // OK — x is number after assignment
256
+ ```
257
+
258
+ ## Narrowing Gotchas
259
+
260
+ ### Narrowing Doesn't Survive Callbacks
261
+
262
+ ```typescript
263
+ function process(value: string | null) {
264
+ if (value !== null) {
265
+ // value is string here
266
+ setTimeout(() => {
267
+ value.toUpperCase(); // Still OK — value is const in closure
268
+ }, 100);
269
+ }
270
+ }
271
+
272
+ // But with reassignable variables:
273
+ let value: string | null = "hello";
274
+ if (value !== null) {
275
+ setTimeout(() => {
276
+ value.toUpperCase(); // Error! value might have been reassigned
277
+ }, 100);
278
+ }
279
+ ```
280
+
281
+ ### Objects and Aliased Conditions
282
+
283
+ ```typescript
284
+ // This doesn't narrow:
285
+ function isValid(obj: { x?: string }) {
286
+ const hasX = obj.x !== undefined;
287
+ if (hasX) {
288
+ obj.x.toUpperCase(); // Error! TypeScript doesn't track aliased conditions
289
+ }
290
+
291
+ // This works:
292
+ if (obj.x !== undefined) {
293
+ obj.x.toUpperCase(); // OK
294
+ }
295
+ }
296
+ ```
297
+
298
+ ### Narrowing with Generics
299
+
300
+ ```typescript
301
+ // TypeScript can't narrow generic types well
302
+ function process<T extends string | number>(value: T) {
303
+ if (typeof value === "string") {
304
+ // value is still T, not string — narrowing is limited with generics
305
+ // Use overloads or conditional types instead
306
+ }
307
+ }
308
+ ```
@@ -0,0 +1,313 @@
1
+ # Common TypeScript Patterns
2
+
3
+ ## Branded Types (Nominal Typing)
4
+
5
+ TypeScript's structural typing means two identical shapes are interchangeable. Branded types add a phantom property to create distinct types:
6
+
7
+ ```typescript
8
+ // Brand type helper
9
+ type Brand<T, B extends string> = T & { readonly __brand: B };
10
+
11
+ // Create distinct ID types
12
+ type UserId = Brand<string, "UserId">;
13
+ type OrderId = Brand<string, "OrderId">;
14
+ type ProductId = Brand<string, "ProductId">;
15
+
16
+ // Constructor functions
17
+ function UserId(id: string): UserId { return id as UserId; }
18
+ function OrderId(id: string): OrderId { return id as OrderId; }
19
+
20
+ // Now they can't be mixed up
21
+ function getOrder(orderId: OrderId): Order { ... }
22
+
23
+ const userId = UserId("user-123");
24
+ const orderId = OrderId("order-456");
25
+
26
+ getOrder(orderId); // OK
27
+ getOrder(userId); // Error! UserId not assignable to OrderId
28
+ ```
29
+
30
+ ### Validated Branded Types
31
+
32
+ Brands can enforce invariants at construction time:
33
+
34
+ ```typescript
35
+ type Email = Brand<string, "Email">;
36
+ type PositiveNumber = Brand<number, "PositiveNumber">;
37
+
38
+ function Email(input: string): Email {
39
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input)) {
40
+ throw new Error(`Invalid email: ${input}`);
41
+ }
42
+ return input as Email;
43
+ }
44
+
45
+ function PositiveNumber(n: number): PositiveNumber {
46
+ if (n <= 0) throw new Error(`Must be positive: ${n}`);
47
+ return n as PositiveNumber;
48
+ }
49
+ ```
50
+
51
+ ## Result Type (Error Handling Without Exceptions)
52
+
53
+ ```typescript
54
+ type Result<T, E = Error> =
55
+ | { success: true; data: T }
56
+ | { success: false; error: E };
57
+
58
+ function ok<T>(data: T): Result<T, never> {
59
+ return { success: true, data };
60
+ }
61
+
62
+ function err<E>(error: E): Result<never, E> {
63
+ return { success: false, error };
64
+ }
65
+
66
+ // Usage
67
+ function parseJSON(input: string): Result<unknown, string> {
68
+ try {
69
+ return ok(JSON.parse(input));
70
+ } catch {
71
+ return err("Invalid JSON");
72
+ }
73
+ }
74
+
75
+ const result = parseJSON('{"a": 1}');
76
+ if (result.success) {
77
+ console.log(result.data); // unknown
78
+ } else {
79
+ console.log(result.error); // string
80
+ }
81
+ ```
82
+
83
+ ## Builder Pattern
84
+
85
+ ```typescript
86
+ class RequestBuilder {
87
+ private config: Partial<RequestConfig> = {};
88
+
89
+ url(url: string): this {
90
+ this.config.url = url;
91
+ return this;
92
+ }
93
+
94
+ method(method: "GET" | "POST" | "PUT" | "DELETE"): this {
95
+ this.config.method = method;
96
+ return this;
97
+ }
98
+
99
+ header(key: string, value: string): this {
100
+ this.config.headers = { ...this.config.headers, [key]: value };
101
+ return this;
102
+ }
103
+
104
+ build(): RequestConfig {
105
+ if (!this.config.url) throw new Error("URL is required");
106
+ return this.config as RequestConfig;
107
+ }
108
+ }
109
+
110
+ // Type-safe builder with required fields tracked in the type
111
+ type Builder<T, Required extends keyof T = never> = {
112
+ [K in keyof T]-?: (value: T[K]) => Builder<T, Required | K>;
113
+ } & ([Required] extends [keyof T]
114
+ ? { build(): T }
115
+ : { build: never });
116
+ ```
117
+
118
+ ## Discriminated Union State Machines
119
+
120
+ ```typescript
121
+ type ConnectionState =
122
+ | { state: "disconnected" }
123
+ | { state: "connecting"; attempt: number }
124
+ | { state: "connected"; socket: WebSocket }
125
+ | { state: "error"; error: Error; retryAfter: number };
126
+
127
+ // Transition functions enforce valid state transitions
128
+ function connect(state: ConnectionState & { state: "disconnected" }): ConnectionState {
129
+ return { state: "connecting", attempt: 1 };
130
+ }
131
+
132
+ function onConnected(
133
+ state: ConnectionState & { state: "connecting" },
134
+ socket: WebSocket
135
+ ): ConnectionState {
136
+ return { state: "connected", socket };
137
+ }
138
+ ```
139
+
140
+ ## Immutable Data
141
+
142
+ ```typescript
143
+ // Readonly utility for shallow immutability
144
+ type Config = Readonly<{
145
+ host: string;
146
+ port: number;
147
+ options: string[];
148
+ }>;
149
+
150
+ // as const for deep immutability of literals
151
+ const ROUTES = {
152
+ home: "/",
153
+ users: "/users",
154
+ userDetail: "/users/:id",
155
+ } as const;
156
+
157
+ type Route = (typeof ROUTES)[keyof typeof ROUTES];
158
+ // "/" | "/users" | "/users/:id"
159
+
160
+ // Readonly collections
161
+ function processItems(items: readonly string[]): void {
162
+ // items.push("x"); // Error! push doesn't exist on readonly array
163
+ items.forEach(console.log); // Reading is fine
164
+ }
165
+ ```
166
+
167
+ ## Type-Safe Event Emitter
168
+
169
+ ```typescript
170
+ type EventMap = Record<string, any[]>;
171
+
172
+ class TypedEmitter<Events extends EventMap> {
173
+ private handlers = new Map<keyof Events, Set<Function>>();
174
+
175
+ on<K extends keyof Events>(event: K, handler: (...args: Events[K]) => void): void {
176
+ if (!this.handlers.has(event)) this.handlers.set(event, new Set());
177
+ this.handlers.get(event)!.add(handler);
178
+ }
179
+
180
+ emit<K extends keyof Events>(event: K, ...args: Events[K]): void {
181
+ this.handlers.get(event)?.forEach(fn => fn(...args));
182
+ }
183
+
184
+ off<K extends keyof Events>(event: K, handler: (...args: Events[K]) => void): void {
185
+ this.handlers.get(event)?.delete(handler);
186
+ }
187
+ }
188
+
189
+ // Usage
190
+ interface AppEvents {
191
+ login: [user: User];
192
+ logout: [];
193
+ error: [code: number, message: string];
194
+ }
195
+
196
+ const emitter = new TypedEmitter<AppEvents>();
197
+ emitter.on("login", (user) => console.log(user.name));
198
+ emitter.on("error", (code, msg) => console.error(code, msg));
199
+ emitter.emit("login", currentUser);
200
+ ```
201
+
202
+ ## Exhaustive Map/Object
203
+
204
+ ```typescript
205
+ // Ensure all enum/union members are mapped
206
+ type Status = "active" | "inactive" | "pending" | "archived";
207
+
208
+ const STATUS_LABELS: Record<Status, string> = {
209
+ active: "Active",
210
+ inactive: "Inactive",
211
+ pending: "Pending",
212
+ archived: "Archived",
213
+ // Missing a key = compile error
214
+ };
215
+
216
+ // Function version
217
+ function getStatusColor(status: Status): string {
218
+ const colors = {
219
+ active: "#00ff00",
220
+ inactive: "#999999",
221
+ pending: "#ffaa00",
222
+ archived: "#ff0000",
223
+ } satisfies Record<Status, string>;
224
+
225
+ return colors[status];
226
+ }
227
+ ```
228
+
229
+ ## Opaque Type Pattern
230
+
231
+ For when you want nominal types that are assignable from their base type in specific contexts:
232
+
233
+ ```typescript
234
+ declare const __opaque: unique symbol;
235
+ type Opaque<T, Token> = T & { readonly [__opaque]: Token };
236
+
237
+ type Seconds = Opaque<number, "Seconds">;
238
+ type Milliseconds = Opaque<number, "Milliseconds">;
239
+
240
+ function wait(duration: Milliseconds): Promise<void> {
241
+ return new Promise(resolve => setTimeout(resolve, duration));
242
+ }
243
+
244
+ function toMilliseconds(seconds: Seconds): Milliseconds {
245
+ return (seconds * 1000) as Milliseconds;
246
+ }
247
+
248
+ const sec = 5 as Seconds;
249
+ wait(sec); // Error! Seconds not assignable to Milliseconds
250
+ wait(toMilliseconds(sec)); // OK
251
+ ```
252
+
253
+ ## Safe Dictionary Access
254
+
255
+ ```typescript
256
+ // With noUncheckedIndexedAccess (recommended)
257
+ const dict: Record<string, string> = { a: "hello" };
258
+ const val = dict["a"]; // string | undefined — forces null check
259
+
260
+ // Safe access pattern
261
+ function getOrThrow<T>(dict: Record<string, T>, key: string): T {
262
+ const value = dict[key];
263
+ if (value === undefined) throw new Error(`Missing key: ${key}`);
264
+ return value;
265
+ }
266
+ ```
267
+
268
+ ## Const Assertions for Enum-Like Objects
269
+
270
+ Prefer `as const` objects over TypeScript enums:
271
+
272
+ ```typescript
273
+ // Instead of:
274
+ enum Direction { North, South, East, West }
275
+
276
+ // Use:
277
+ const Direction = {
278
+ North: "north",
279
+ South: "south",
280
+ East: "east",
281
+ West: "west",
282
+ } as const;
283
+
284
+ type Direction = (typeof Direction)[keyof typeof Direction];
285
+ // "north" | "south" | "east" | "west"
286
+
287
+ // Benefits:
288
+ // - No runtime code beyond the object
289
+ // - Values are string literals (not numbers)
290
+ // - Easily iterable with Object.values()
291
+ // - Works with JSON serialization
292
+ ```
293
+
294
+ ## Assertion Signatures for Validation
295
+
296
+ ```typescript
297
+ function assertNotNull<T>(value: T, message?: string): asserts value is NonNullable<T> {
298
+ if (value == null) throw new Error(message ?? "Unexpected null");
299
+ }
300
+
301
+ function assertType<T>(value: unknown, check: (v: unknown) => v is T): asserts value is T {
302
+ if (!check(value)) throw new Error("Type assertion failed");
303
+ }
304
+
305
+ // Chain assertions for parsing
306
+ function parseConfig(raw: unknown): Config {
307
+ assertType(raw, isObject);
308
+ assertNotNull(raw.host);
309
+ assertType(raw.host, isString);
310
+ assertType(raw.port, isNumber);
311
+ return raw as Config;
312
+ }
313
+ ```