@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.
- package/README.md +5 -1
- package/package.json +1 -1
- package/registry/registry.json +15 -0
- package/specs/coding.md +11 -0
- package/src/commands/skills/add.js +115 -31
- package/src/commands/skills/list.js +25 -52
- package/src/commands/skills/remove.js +45 -27
- package/src/commands/skills/resolve.js +104 -0
- package/src/commands/skills/update.js +58 -74
- package/src/config.js +5 -0
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# TC39 Decorators (TypeScript 5.0+)
|
|
2
|
+
|
|
3
|
+
TypeScript 5.0 introduced support for the TC39 Stage 3 decorator proposal. These are **not** the same as legacy `experimentalDecorators` — they have different semantics, no `reflect-metadata`, and work without any compiler flag.
|
|
4
|
+
|
|
5
|
+
## When to Use Which
|
|
6
|
+
|
|
7
|
+
- **TC39 decorators** (default, no flag): The standard going forward. Use for new code.
|
|
8
|
+
- **`experimentalDecorators`** (tsconfig flag): Legacy. Required by Angular, NestJS, TypeORM, and other frameworks that depend on `reflect-metadata`. Keep using if your framework requires it.
|
|
9
|
+
|
|
10
|
+
Check your framework's documentation — many are migrating to TC39 decorators.
|
|
11
|
+
|
|
12
|
+
## Class Decorators
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
// A class decorator receives the class itself and an optional context
|
|
16
|
+
function sealed(target: Function, context: ClassDecoratorContext) {
|
|
17
|
+
Object.seal(target);
|
|
18
|
+
Object.seal(target.prototype);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@sealed
|
|
22
|
+
class Greeter {
|
|
23
|
+
greeting: string;
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
this.greeting = message;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Adding Functionality
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
function withTimestamp<T extends new (...args: any[]) => any>(
|
|
34
|
+
target: T,
|
|
35
|
+
context: ClassDecoratorContext
|
|
36
|
+
) {
|
|
37
|
+
return class extends target {
|
|
38
|
+
createdAt = new Date();
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@withTimestamp
|
|
43
|
+
class User {
|
|
44
|
+
name: string;
|
|
45
|
+
constructor(name: string) { this.name = name; }
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Method Decorators
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
function log(
|
|
53
|
+
target: Function,
|
|
54
|
+
context: ClassMethodDecoratorContext
|
|
55
|
+
) {
|
|
56
|
+
const methodName = String(context.name);
|
|
57
|
+
return function (this: any, ...args: any[]) {
|
|
58
|
+
console.log(`Calling ${methodName} with`, args);
|
|
59
|
+
const result = target.call(this, ...args);
|
|
60
|
+
console.log(`${methodName} returned`, result);
|
|
61
|
+
return result;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
class Calculator {
|
|
66
|
+
@log
|
|
67
|
+
add(a: number, b: number): number {
|
|
68
|
+
return a + b;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Bound Method Decorator
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
function bound(
|
|
77
|
+
target: Function,
|
|
78
|
+
context: ClassMethodDecoratorContext
|
|
79
|
+
) {
|
|
80
|
+
const methodName = context.name;
|
|
81
|
+
context.addInitializer(function (this: any) {
|
|
82
|
+
this[methodName] = this[methodName].bind(this);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class Button {
|
|
87
|
+
label = "Click me";
|
|
88
|
+
|
|
89
|
+
@bound
|
|
90
|
+
handleClick() {
|
|
91
|
+
console.log(this.label); // Always correct `this`
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Field Decorators
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
function min(minValue: number) {
|
|
100
|
+
return function (
|
|
101
|
+
target: undefined, // field decorators receive undefined
|
|
102
|
+
context: ClassFieldDecoratorContext
|
|
103
|
+
) {
|
|
104
|
+
return function (initialValue: number) {
|
|
105
|
+
if (initialValue < minValue) {
|
|
106
|
+
throw new Error(`${String(context.name)} must be >= ${minValue}`);
|
|
107
|
+
}
|
|
108
|
+
return initialValue;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class Product {
|
|
114
|
+
@min(0)
|
|
115
|
+
price: number;
|
|
116
|
+
|
|
117
|
+
constructor(price: number) {
|
|
118
|
+
this.price = price;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Accessor Decorators
|
|
124
|
+
|
|
125
|
+
The `accessor` keyword (TS 5.0+) creates auto-accessor fields with implicit getter/setter:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
class Person {
|
|
129
|
+
accessor name: string;
|
|
130
|
+
|
|
131
|
+
constructor(name: string) {
|
|
132
|
+
this.name = name;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Decorator for accessors
|
|
137
|
+
function validate(
|
|
138
|
+
target: ClassAccessorDecoratorTarget<any, string>,
|
|
139
|
+
context: ClassAccessorDecoratorContext
|
|
140
|
+
) {
|
|
141
|
+
return {
|
|
142
|
+
set(value: string) {
|
|
143
|
+
if (!value.trim()) throw new Error(`${String(context.name)} cannot be empty`);
|
|
144
|
+
target.set.call(this, value);
|
|
145
|
+
},
|
|
146
|
+
get() {
|
|
147
|
+
return target.get.call(this);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
class User {
|
|
153
|
+
@validate
|
|
154
|
+
accessor name: string = "";
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Decorator Factories
|
|
159
|
+
|
|
160
|
+
Most real-world decorators are factories — functions that return decorators:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
function retry(attempts: number) {
|
|
164
|
+
return function (
|
|
165
|
+
target: Function,
|
|
166
|
+
context: ClassMethodDecoratorContext
|
|
167
|
+
) {
|
|
168
|
+
return async function (this: any, ...args: any[]) {
|
|
169
|
+
for (let i = 0; i < attempts; i++) {
|
|
170
|
+
try {
|
|
171
|
+
return await target.call(this, ...args);
|
|
172
|
+
} catch (err) {
|
|
173
|
+
if (i === attempts - 1) throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
class ApiClient {
|
|
181
|
+
@retry(3)
|
|
182
|
+
async fetchData(url: string): Promise<Response> {
|
|
183
|
+
return fetch(url);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Decorator Context Types
|
|
189
|
+
|
|
190
|
+
Each decorator kind has a specific context type:
|
|
191
|
+
|
|
192
|
+
| Decorator Target | Context Type |
|
|
193
|
+
|------------------|-------------|
|
|
194
|
+
| Class | `ClassDecoratorContext` |
|
|
195
|
+
| Method | `ClassMethodDecoratorContext` |
|
|
196
|
+
| Getter | `ClassGetterDecoratorContext` |
|
|
197
|
+
| Setter | `ClassSetterDecoratorContext` |
|
|
198
|
+
| Field | `ClassFieldDecoratorContext` |
|
|
199
|
+
| Auto-accessor | `ClassAccessorDecoratorContext` |
|
|
200
|
+
|
|
201
|
+
All context types include:
|
|
202
|
+
- `name`: The name of the decorated element
|
|
203
|
+
- `kind`: "class", "method", "getter", "setter", "field", or "accessor"
|
|
204
|
+
- `static`: Whether the element is static
|
|
205
|
+
- `private`: Whether the element is private
|
|
206
|
+
- `addInitializer()`: Register a callback to run during construction
|
|
207
|
+
- `metadata`: Shared metadata object (replaces `reflect-metadata`)
|
|
208
|
+
|
|
209
|
+
## Metadata (TC39 Decorator Metadata)
|
|
210
|
+
|
|
211
|
+
TC39 decorators have a built-in metadata mechanism:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
function meta(key: string, value: any) {
|
|
215
|
+
return function (_target: any, context: ClassMethodDecoratorContext) {
|
|
216
|
+
context.metadata[key] = value;
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
class Routes {
|
|
221
|
+
@meta("path", "/users")
|
|
222
|
+
@meta("method", "GET")
|
|
223
|
+
getUsers() { ... }
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Access metadata
|
|
227
|
+
const metadata = Routes[Symbol.metadata];
|
|
228
|
+
// { path: "/users", method: "GET" }
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Migration from `experimentalDecorators`
|
|
232
|
+
|
|
233
|
+
Key differences:
|
|
234
|
+
|
|
235
|
+
| `experimentalDecorators` | TC39 Decorators |
|
|
236
|
+
|--------------------------|-----------------|
|
|
237
|
+
| Receives `(target, key, descriptor)` | Receives `(value, context)` |
|
|
238
|
+
| Uses `reflect-metadata` for metadata | Uses `context.metadata` |
|
|
239
|
+
| Parameter decorators supported | No parameter decorators |
|
|
240
|
+
| `emitDecoratorMetadata` flag | No equivalent (use `context.metadata`) |
|
|
241
|
+
| Runs at class definition time | Runs at class definition time |
|
|
242
|
+
|
|
243
|
+
Parameter decorators are **not** part of TC39 decorators. Frameworks that need them (like NestJS for DI) still require `experimentalDecorators`.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Mapped Types
|
|
2
|
+
|
|
3
|
+
Mapped types transform every property in an existing type, producing a new type. They iterate over keys and apply transformations.
|
|
4
|
+
|
|
5
|
+
## Basic Syntax
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
type Mapped<T> = {
|
|
9
|
+
[K in keyof T]: T[K];
|
|
10
|
+
};
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This is the identity mapped type — it produces the same type. The power comes from modifying the value type or the key.
|
|
14
|
+
|
|
15
|
+
## Adding/Removing Modifiers
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Add readonly to all properties
|
|
19
|
+
type Readonly<T> = {
|
|
20
|
+
readonly [K in keyof T]: T[K];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Remove readonly with -readonly
|
|
24
|
+
type Mutable<T> = {
|
|
25
|
+
-readonly [K in keyof T]: T[K];
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Make all properties optional
|
|
29
|
+
type Partial<T> = {
|
|
30
|
+
[K in keyof T]?: T[K];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Remove optionality with -?
|
|
34
|
+
type Required<T> = {
|
|
35
|
+
[K in keyof T]-?: T[K];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Combine: mutable and required
|
|
39
|
+
type Concrete<T> = {
|
|
40
|
+
-readonly [K in keyof T]-?: T[K];
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Key Remapping with `as` (TS 4.1+)
|
|
45
|
+
|
|
46
|
+
Remap keys during iteration using `as`:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Rename keys with a template literal
|
|
50
|
+
type Getters<T> = {
|
|
51
|
+
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
interface Person { name: string; age: number }
|
|
55
|
+
type PersonGetters = Getters<Person>;
|
|
56
|
+
// { getName: () => string; getAge: () => number }
|
|
57
|
+
|
|
58
|
+
// Filter keys by remapping to never
|
|
59
|
+
type RemoveFunctions<T> = {
|
|
60
|
+
[K in keyof T as T[K] extends Function ? never : K]: T[K];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface Mixed {
|
|
64
|
+
name: string;
|
|
65
|
+
age: number;
|
|
66
|
+
greet(): void;
|
|
67
|
+
}
|
|
68
|
+
type DataOnly = RemoveFunctions<Mixed>;
|
|
69
|
+
// { name: string; age: number }
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Iterating Over Unions
|
|
73
|
+
|
|
74
|
+
Mapped types can iterate over any union of string literals, not just `keyof`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
type EventMap = {
|
|
78
|
+
[K in "click" | "hover" | "focus"]: (e: Event) => void;
|
|
79
|
+
};
|
|
80
|
+
// { click: (e: Event) => void; hover: (e: Event) => void; focus: (e: Event) => void }
|
|
81
|
+
|
|
82
|
+
// Using a union of string literals from an enum-like const
|
|
83
|
+
const EVENTS = ["click", "hover", "focus"] as const;
|
|
84
|
+
type EventHandlers = {
|
|
85
|
+
[K in (typeof EVENTS)[number] as `on${Capitalize<K>}`]: (e: Event) => void;
|
|
86
|
+
};
|
|
87
|
+
// { onClick: (e: Event) => void; onHover: (e: Event) => void; onFocus: (e: Event) => void }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Practical Patterns
|
|
91
|
+
|
|
92
|
+
### Making Specific Properties Optional
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
type OptionalBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
96
|
+
|
|
97
|
+
interface User {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
email: string;
|
|
101
|
+
}
|
|
102
|
+
type CreateUser = OptionalBy<User, "id">;
|
|
103
|
+
// { name: string; email: string; id?: string }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Deep Partial
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
type DeepPartial<T> = {
|
|
110
|
+
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
interface Config {
|
|
114
|
+
server: { host: string; port: number };
|
|
115
|
+
db: { url: string; pool: { min: number; max: number } };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type PartialConfig = DeepPartial<Config>;
|
|
119
|
+
// All nested properties are optional
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Deep Readonly
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
type DeepReadonly<T> = {
|
|
126
|
+
readonly [K in keyof T]: T[K] extends object
|
|
127
|
+
? T[K] extends Function
|
|
128
|
+
? T[K]
|
|
129
|
+
: DeepReadonly<T[K]>
|
|
130
|
+
: T[K];
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Nullable Properties
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
type Nullable<T> = {
|
|
138
|
+
[K in keyof T]: T[K] | null;
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Event Emitter Types
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
type EventEmitter<Events extends Record<string, any[]>> = {
|
|
146
|
+
on<K extends keyof Events>(event: K, handler: (...args: Events[K]) => void): void;
|
|
147
|
+
emit<K extends keyof Events>(event: K, ...args: Events[K]): void;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
interface MyEvents {
|
|
151
|
+
login: [user: User];
|
|
152
|
+
error: [code: number, message: string];
|
|
153
|
+
logout: [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
declare const emitter: EventEmitter<MyEvents>;
|
|
157
|
+
emitter.on("login", (user) => { ... }); // user: User
|
|
158
|
+
emitter.on("error", (code, msg) => { ... }); // code: number, msg: string
|
|
159
|
+
emitter.emit("logout"); // no args
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### API Route Types
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
type ApiRoutes = {
|
|
166
|
+
"/users": { GET: User[]; POST: User };
|
|
167
|
+
"/users/:id": { GET: User; PUT: User; DELETE: void };
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
type RouteHandler<
|
|
171
|
+
Routes extends Record<string, Record<string, any>>,
|
|
172
|
+
Path extends keyof Routes,
|
|
173
|
+
Method extends keyof Routes[Path]
|
|
174
|
+
> = () => Promise<Routes[Path][Method]>;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Record-Like with Constraints
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Like Record, but values depend on the key
|
|
181
|
+
type TypedRecord<K extends string, ValueFn extends Record<K, any>> = {
|
|
182
|
+
[P in K]: ValueFn[P];
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Each validator returns the type it validates
|
|
186
|
+
type Validators = TypedRecord<
|
|
187
|
+
"name" | "age",
|
|
188
|
+
{ name: string; age: number }
|
|
189
|
+
>;
|
|
190
|
+
// { name: string; age: number }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Combining with Conditional Types
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Make all function properties async
|
|
197
|
+
type Asyncify<T> = {
|
|
198
|
+
[K in keyof T]: T[K] extends (...args: infer A) => infer R
|
|
199
|
+
? (...args: A) => Promise<R>
|
|
200
|
+
: T[K];
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
interface Sync {
|
|
204
|
+
getData(): string;
|
|
205
|
+
count: number;
|
|
206
|
+
}
|
|
207
|
+
type Async = Asyncify<Sync>;
|
|
208
|
+
// { getData: () => Promise<string>; count: number }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Homomorphic vs Non-Homomorphic
|
|
212
|
+
|
|
213
|
+
A mapped type is **homomorphic** when it maps over `keyof T` (preserving modifiers from the original):
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// Homomorphic — preserves optional/readonly from T
|
|
217
|
+
type Clone<T> = { [K in keyof T]: T[K] };
|
|
218
|
+
|
|
219
|
+
// Non-homomorphic — uses an independent key set
|
|
220
|
+
type FromKeys<K extends string> = { [P in K]: unknown };
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Homomorphic mapped types automatically preserve `readonly` and `?` modifiers unless explicitly removed with `-readonly` or `-?`.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Template Literal Types
|
|
2
|
+
|
|
3
|
+
Template literal types build string types from other types using template literal syntax. They're TypeScript's most powerful tool for type-safe string manipulation.
|
|
4
|
+
|
|
5
|
+
## Basic Syntax
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
type Greeting = `Hello, ${string}`;
|
|
9
|
+
// Matches "Hello, Alice", "Hello, Bob", "Hello, " — any string after "Hello, "
|
|
10
|
+
|
|
11
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
12
|
+
type Endpoint = `/api/${string}`;
|
|
13
|
+
type ApiCall = `${HttpMethod} ${Endpoint}`;
|
|
14
|
+
// "GET /api/..." | "POST /api/..." | "PUT /api/..." | "DELETE /api/..."
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Union Expansion
|
|
18
|
+
|
|
19
|
+
When unions appear in template literal positions, the result is the cross product:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
type Suit = "hearts" | "diamonds" | "clubs" | "spades";
|
|
23
|
+
type Rank = "A" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" | "10" | "J" | "Q" | "K";
|
|
24
|
+
type Card = `${Rank} of ${Suit}`;
|
|
25
|
+
// "A of hearts" | "A of diamonds" | ... | "K of spades" (52 members)
|
|
26
|
+
|
|
27
|
+
type Size = "sm" | "md" | "lg";
|
|
28
|
+
type Color = "red" | "blue" | "green";
|
|
29
|
+
type Variant = `${Size}-${Color}`;
|
|
30
|
+
// "sm-red" | "sm-blue" | "sm-green" | "md-red" | ... (9 members)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Intrinsic String Manipulation Types
|
|
34
|
+
|
|
35
|
+
TypeScript provides four built-in types that transform string literals:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
type A = Uppercase<"hello">; // "HELLO"
|
|
39
|
+
type B = Lowercase<"HELLO">; // "hello"
|
|
40
|
+
type C = Capitalize<"hello">; // "Hello"
|
|
41
|
+
type D = Uncapitalize<"Hello">; // "hello"
|
|
42
|
+
|
|
43
|
+
// Combined with template literals
|
|
44
|
+
type EventHandler<T extends string> = `on${Capitalize<T>}`;
|
|
45
|
+
type Click = EventHandler<"click">; // "onClick"
|
|
46
|
+
type KeyDown = EventHandler<"keyDown">; // "onKeyDown"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Pattern Matching with `infer`
|
|
50
|
+
|
|
51
|
+
Template literals can extract parts of string types:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Extract the event name from "on{Event}"
|
|
55
|
+
type ExtractEvent<T> = T extends `on${infer E}` ? Uncapitalize<E> : never;
|
|
56
|
+
type A = ExtractEvent<"onClick">; // "click"
|
|
57
|
+
type B = ExtractEvent<"onKeyDown">; // "keyDown"
|
|
58
|
+
type C = ExtractEvent<"submit">; // never
|
|
59
|
+
|
|
60
|
+
// Parse dot-separated paths
|
|
61
|
+
type FirstSegment<T extends string> = T extends `${infer Head}.${string}` ? Head : T;
|
|
62
|
+
type D = FirstSegment<"user.address.city">; // "user"
|
|
63
|
+
type E = FirstSegment<"name">; // "name"
|
|
64
|
+
|
|
65
|
+
// Split a string type
|
|
66
|
+
type Split<S extends string, D extends string> =
|
|
67
|
+
S extends `${infer Head}${D}${infer Tail}`
|
|
68
|
+
? [Head, ...Split<Tail, D>]
|
|
69
|
+
: [S];
|
|
70
|
+
|
|
71
|
+
type F = Split<"a.b.c", ".">; // ["a", "b", "c"]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Practical Patterns
|
|
75
|
+
|
|
76
|
+
### Type-Safe Event System
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
type EventMap = {
|
|
80
|
+
click: { x: number; y: number };
|
|
81
|
+
keydown: { key: string };
|
|
82
|
+
resize: { width: number; height: number };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
type EventHandlerName<T extends string> = `on${Capitalize<T>}`;
|
|
86
|
+
|
|
87
|
+
type EventHandlers<E extends Record<string, any>> = {
|
|
88
|
+
[K in keyof E as EventHandlerName<string & K>]?: (event: E[K]) => void;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
type MyHandlers = EventHandlers<EventMap>;
|
|
92
|
+
// { onClick?: (event: { x: number; y: number }) => void;
|
|
93
|
+
// onKeydown?: (event: { key: string }) => void;
|
|
94
|
+
// onResize?: (event: { width: number; height: number }) => void; }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Type-Safe CSS Properties
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
type CSSUnit = "px" | "em" | "rem" | "%" | "vh" | "vw";
|
|
101
|
+
type CSSValue = `${number}${CSSUnit}` | "auto" | "inherit";
|
|
102
|
+
|
|
103
|
+
function setWidth(el: HTMLElement, width: CSSValue) {
|
|
104
|
+
el.style.width = width;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setWidth(el, "100px"); // OK
|
|
108
|
+
setWidth(el, "2.5rem"); // OK
|
|
109
|
+
setWidth(el, "auto"); // OK
|
|
110
|
+
setWidth(el, "100"); // Error — missing unit
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Route Parameter Extraction
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
type ExtractParams<T extends string> =
|
|
117
|
+
T extends `${string}:${infer Param}/${infer Rest}`
|
|
118
|
+
? Param | ExtractParams<`/${Rest}`>
|
|
119
|
+
: T extends `${string}:${infer Param}`
|
|
120
|
+
? Param
|
|
121
|
+
: never;
|
|
122
|
+
|
|
123
|
+
type Params = ExtractParams<"/users/:userId/posts/:postId">;
|
|
124
|
+
// "userId" | "postId"
|
|
125
|
+
|
|
126
|
+
type RouteParams<T extends string> = {
|
|
127
|
+
[K in ExtractParams<T>]: string;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
type UserPostParams = RouteParams<"/users/:userId/posts/:postId">;
|
|
131
|
+
// { userId: string; postId: string }
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### SQL Column Type Mapping
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
type SQLType = "TEXT" | "INTEGER" | "BOOLEAN" | "TIMESTAMP";
|
|
138
|
+
|
|
139
|
+
type TSTypeMap = {
|
|
140
|
+
TEXT: string;
|
|
141
|
+
INTEGER: number;
|
|
142
|
+
BOOLEAN: boolean;
|
|
143
|
+
TIMESTAMP: Date;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type ColumnDef<Name extends string, Type extends SQLType> = `${Name} ${Type}`;
|
|
147
|
+
|
|
148
|
+
type ParseColumn<T> = T extends `${infer Name} ${infer Type extends SQLType}`
|
|
149
|
+
? { name: Name; type: TSTypeMap[Type] }
|
|
150
|
+
: never;
|
|
151
|
+
|
|
152
|
+
type Col = ParseColumn<"username TEXT">;
|
|
153
|
+
// { name: "username"; type: string }
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Deep Property Paths
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
type PropPath<T, Prefix extends string = ""> = {
|
|
160
|
+
[K in keyof T & string]: T[K] extends object
|
|
161
|
+
? PropPath<T[K], `${Prefix}${K}.`>
|
|
162
|
+
: `${Prefix}${K}`;
|
|
163
|
+
}[keyof T & string];
|
|
164
|
+
|
|
165
|
+
interface User {
|
|
166
|
+
name: string;
|
|
167
|
+
address: {
|
|
168
|
+
city: string;
|
|
169
|
+
zip: string;
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
type UserPaths = PropPath<User>;
|
|
174
|
+
// "name" | "address.city" | "address.zip"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Template Literals as Discriminants (TS 4.5+)
|
|
178
|
+
|
|
179
|
+
Template literal types can serve as discriminants in unions:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface SuccessResponse {
|
|
183
|
+
type: `${string}Success`;
|
|
184
|
+
data: unknown;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface ErrorResponse {
|
|
188
|
+
type: `${string}Error`;
|
|
189
|
+
message: string;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handle(r: SuccessResponse | ErrorResponse) {
|
|
193
|
+
if (r.type === "ApiSuccess") {
|
|
194
|
+
r.data; // SuccessResponse narrowed
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Performance Considerations
|
|
200
|
+
|
|
201
|
+
Template literal unions grow multiplicatively. A cross product of two unions with 10 members each creates 100 members. TypeScript limits union sizes (around 100,000 members), so avoid unbounded cross products:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// This is fine — 3 x 3 = 9 members
|
|
205
|
+
type Small = `${1 | 2 | 3}-${"a" | "b" | "c"}`;
|
|
206
|
+
|
|
207
|
+
// This would be problematic — string has infinite members
|
|
208
|
+
// type Bad = `${string}-${string}`; // Works but can't enumerate
|
|
209
|
+
```
|