claude-toolkit 0.1.9
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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bin/cli.ts +112 -0
- package/core/agents/ct-code-reviewer.md +123 -0
- package/core/agents/ct-github-workflow.md +137 -0
- package/core/commands/ct/code-quality.md +59 -0
- package/core/commands/ct/onboard.md +84 -0
- package/core/commands/ct/pr-review.md +104 -0
- package/core/commands/ct/pr-summary.md +59 -0
- package/core/commands/ct/proto-check.md +74 -0
- package/core/commands/ct/ticket.md +71 -0
- package/core/hooks/skill-eval.js +381 -0
- package/core/hooks/skill-eval.sh +35 -0
- package/core/hooks/skill-rules.schema.json +112 -0
- package/core/skills/systematic-debugging/SKILL.md +44 -0
- package/core/skills/testing-patterns/SKILL.md +52 -0
- package/core/skills/typescript-conventions/SKILL.md +57 -0
- package/core/skills/verification-before-completion/SKILL.md +42 -0
- package/docs/README.md +49 -0
- package/docs/agents/code-reviewer.md +76 -0
- package/docs/agents/github-workflow.md +98 -0
- package/docs/best-practices/solidjs/README.md +43 -0
- package/docs/best-practices/solidjs/anti-patterns.md +166 -0
- package/docs/best-practices/solidjs/component-patterns.md +131 -0
- package/docs/best-practices/solidjs/context-and-global-state.md +131 -0
- package/docs/best-practices/solidjs/control-flow.md +124 -0
- package/docs/best-practices/solidjs/data-fetching.md +205 -0
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +113 -0
- package/docs/best-practices/solidjs/performance.md +100 -0
- package/docs/best-practices/solidjs/props-patterns.md +100 -0
- package/docs/best-practices/solidjs/reactivity-model.md +104 -0
- package/docs/best-practices/solidjs/signals-and-state.md +78 -0
- package/docs/best-practices/solidjs/stores-and-nested-state.md +111 -0
- package/docs/best-practices/solidjs/typescript-integration.md +186 -0
- package/docs/best-practices/typescript/README.md +45 -0
- package/docs/best-practices/typescript/any-and-unknown.md +73 -0
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +83 -0
- package/docs/best-practices/typescript/discriminated-unions.md +75 -0
- package/docs/best-practices/typescript/enums-alternatives.md +72 -0
- package/docs/best-practices/typescript/essential-patterns.md +119 -0
- package/docs/best-practices/typescript/generics-patterns.md +105 -0
- package/docs/best-practices/typescript/micro-opinions.md +87 -0
- package/docs/best-practices/typescript/runtime-validation.md +62 -0
- package/docs/best-practices/typescript/satisfies-operator.md +100 -0
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +129 -0
- package/docs/best-practices/typescript/type-organization.md +64 -0
- package/docs/best-practices/typescript/type-vs-interface.md +80 -0
- package/docs/commands/code-quality.md +42 -0
- package/docs/commands/onboard.md +72 -0
- package/docs/commands/pr-review.md +102 -0
- package/docs/commands/pr-summary.md +50 -0
- package/docs/commands/proto-check.md +59 -0
- package/docs/commands/ticket.md +56 -0
- package/docs/skills/systematic-debugging.md +70 -0
- package/docs/skills/testing-patterns.md +89 -0
- package/docs/skills/typescript-conventions.md +137 -0
- package/docs/skills/verification-before-completion.md +91 -0
- package/docs/stacks/cloudflare-d1-kv.md +110 -0
- package/docs/stacks/i18n-typesafe.md +141 -0
- package/docs/stacks/protobuf-contracts.md +85 -0
- package/docs/stacks/rust-wasm-patterns.md +106 -0
- package/docs/stacks/solidjs-patterns.md +110 -0
- package/docs/stacks/vanilla-extract-patterns.md +115 -0
- package/package.json +58 -0
- package/src/generator.ts +317 -0
- package/src/index.ts +30 -0
- package/src/types.ts +85 -0
- package/src/utils.ts +53 -0
- package/stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md +84 -0
- package/stacks/cloudflare/stack.json +26 -0
- package/stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md +64 -0
- package/stacks/i18n-typesafe/stack.json +25 -0
- package/stacks/protobuf/skills/protobuf-contracts/SKILL.md +78 -0
- package/stacks/protobuf/stack.json +25 -0
- package/stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md +76 -0
- package/stacks/rust-wasm/stack.json +26 -0
- package/stacks/solidjs/skills/solidjs-patterns/SKILL.md +66 -0
- package/stacks/solidjs/stack.json +52 -0
- package/stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md +76 -0
- package/stacks/vanilla-extract/stack.json +40 -0
- package/templates/claude-toolkit.config.ts +34 -0
- package/templates/configs/biome.base.json +35 -0
- package/templates/configs/tsconfig.base.json +16 -0
- package/templates/skill-rules.base.json +98 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Essential Patterns
|
|
2
|
+
|
|
3
|
+
> Source: [Four Essential TypeScript Patterns You Can't Work Without](https://www.totaltypescript.com/four-essential-typescript-patterns) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## 1. Branded Types
|
|
6
|
+
|
|
7
|
+
Create **validation boundaries** by tagging base types with a unique label. Prevents mixing validated and unvalidated values of the same underlying type.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
type Brand<T, TBrand extends string> = T & { __brand: TBrand };
|
|
11
|
+
|
|
12
|
+
type Password = Brand<string, "Password">;
|
|
13
|
+
type Email = Brand<string, "Email">;
|
|
14
|
+
|
|
15
|
+
function validatePassword(input: string): Password {
|
|
16
|
+
if (input.length < 8) throw new Error("Too short");
|
|
17
|
+
return input as Password;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function login(email: Email, password: Password) {
|
|
21
|
+
// Both values are guaranteed to have been validated
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// login("user@test.com", "short") — Error! Raw strings are not Password/Email
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**When to use:** Anywhere you need to ensure a value has passed through validation — passwords, emails, sanitized HTML, monetary amounts, database IDs.
|
|
28
|
+
|
|
29
|
+
## 2. Globals
|
|
30
|
+
|
|
31
|
+
Understand and manage TypeScript's **global type scope** for typing JavaScript globals like `process.env`, `window`, or custom global functions.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
declare global {
|
|
35
|
+
function myGlobalFunc(): boolean;
|
|
36
|
+
var myGlobalVar: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Now available everywhere without imports
|
|
40
|
+
console.log(myGlobalVar);
|
|
41
|
+
myGlobalFunc();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Practical use — strongly typing `process.env`:**
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
declare global {
|
|
48
|
+
namespace NodeJS {
|
|
49
|
+
interface ProcessEnv {
|
|
50
|
+
DATABASE_URL: string;
|
|
51
|
+
API_KEY: string;
|
|
52
|
+
NODE_ENV: "development" | "production" | "test";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// process.env.DATABASE_URL is now typed as string (not string | undefined)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 3. Assertion Functions & Type Predicates
|
|
61
|
+
|
|
62
|
+
Both patterns improve TypeScript's ability to narrow types.
|
|
63
|
+
|
|
64
|
+
### Type Predicates
|
|
65
|
+
|
|
66
|
+
Narrow types within conditionals using `is`:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const values = ["a", "b", undefined, "c", undefined];
|
|
70
|
+
|
|
71
|
+
const filtered = values.filter((v): v is string => Boolean(v));
|
|
72
|
+
// string[] — TypeScript knows undefined was removed
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Assertion Functions
|
|
76
|
+
|
|
77
|
+
Enforce type guarantees or throw errors using `asserts`:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
function assertIsString(value: unknown): asserts value is string {
|
|
81
|
+
if (typeof value !== "string") {
|
|
82
|
+
throw new Error(`Expected string, got ${typeof value}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function process(input: unknown) {
|
|
87
|
+
assertIsString(input);
|
|
88
|
+
// input is narrowed to string from here on
|
|
89
|
+
console.log(input.toUpperCase());
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Key difference:** Predicates return `boolean` and work in `if`/`filter`. Assertions return `void` and narrow the type for all subsequent code in the scope.
|
|
94
|
+
|
|
95
|
+
## 4. Classes (for Library APIs)
|
|
96
|
+
|
|
97
|
+
Classes enable the **builder pattern** and self-typing structures — particularly useful in library code.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
class SDK {
|
|
101
|
+
loggedInUser?: User;
|
|
102
|
+
|
|
103
|
+
constructor(loggedInUser?: User) {
|
|
104
|
+
this.loggedInUser = loggedInUser;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assertIsLoggedIn(): asserts this is this & { loggedInUser: User } {
|
|
108
|
+
if (!this.loggedInUser) {
|
|
109
|
+
throw new Error("Not logged in");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const sdk = new SDK();
|
|
115
|
+
sdk.assertIsLoggedIn();
|
|
116
|
+
// sdk.loggedInUser is now User (not User | undefined)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**When to use:** Libraries and SDKs where you need fluent APIs, chainable methods, or internal type refinement. This pattern powers libraries like tRPC.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Generics Patterns
|
|
2
|
+
|
|
3
|
+
> Sources: [TypeScript Generics in 3 Easy Patterns](https://www.totaltypescript.com/typescript-generics-in-three-easy-patterns), [There Is No Such Thing As A Generic](https://www.totaltypescript.com/no-such-thing-as-a-generic) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## Mental Model
|
|
6
|
+
|
|
7
|
+
Matt argues that "generics" is a misleading name. What people call generics is actually **three distinct patterns** for passing type arguments around.
|
|
8
|
+
|
|
9
|
+
## Pattern 1: Passing Types to Types
|
|
10
|
+
|
|
11
|
+
Create reusable type definitions that accept type parameters — like function arguments, but for types.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
type ApiResponse<TData> = {
|
|
15
|
+
data: TData;
|
|
16
|
+
error?: { message: string };
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type UserResponse = ApiResponse<{
|
|
20
|
+
id: string;
|
|
21
|
+
name: string;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
type OrderResponse = ApiResponse<{
|
|
25
|
+
orderId: string;
|
|
26
|
+
total: number;
|
|
27
|
+
}>;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This eliminates repetitive wrapper types while keeping each response strongly typed.
|
|
31
|
+
|
|
32
|
+
## Pattern 2: Passing Types to Functions
|
|
33
|
+
|
|
34
|
+
Explicitly specify type arguments when calling a function:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const createSet = <T>() => {
|
|
38
|
+
return new Set<T>();
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const stringSet = createSet<string>();
|
|
42
|
+
const numberSet = createSet<number>();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Use this when TypeScript has no value to infer from — the function takes no arguments of type `T`.
|
|
46
|
+
|
|
47
|
+
## Pattern 3: Inferring Types from Arguments
|
|
48
|
+
|
|
49
|
+
The most common and powerful pattern. TypeScript deduces type parameters from the values you pass:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const createSet = <T>(initial: T) => {
|
|
53
|
+
return new Set<T>([initial]);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const stringSet = createSet("matt"); // Set<string>
|
|
57
|
+
const numberSet = createSet(123); // Set<number>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
A practical example with constraints:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const objKeys = <T extends object>(obj: T): Array<keyof T> => {
|
|
64
|
+
return Object.keys(obj) as Array<keyof T>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const keys = objKeys({ a: 1, b: 2 });
|
|
68
|
+
// ("a" | "b")[]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The `extends object` constraint ensures only objects are accepted, while `keyof T` preserves the specific key types.
|
|
72
|
+
|
|
73
|
+
## When to Use Generics vs Unions
|
|
74
|
+
|
|
75
|
+
From Matt's tip "Know when to use generics":
|
|
76
|
+
|
|
77
|
+
**Use generics** when the output type depends on the input type — the caller's choice flows through the function.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// Generic: return type depends on input
|
|
81
|
+
function first<T>(arr: T[]): T | undefined {
|
|
82
|
+
return arr[0];
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Use unions** when the function handles a fixed set of types internally.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
// Union: function handles all cases itself
|
|
90
|
+
function format(value: string | number): string {
|
|
91
|
+
return String(value);
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Constraining Generics
|
|
96
|
+
|
|
97
|
+
Use `extends` to document expectations and catch misuse at the call site:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
function merge<T extends object, U extends object>(a: T, b: U): T & U {
|
|
101
|
+
return { ...a, ...b };
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Without the constraint, callers could pass primitives and get confusing errors deep inside the implementation.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Micro-Opinions
|
|
2
|
+
|
|
3
|
+
> Small but specific stances from Matt Pocock on everyday TypeScript choices.
|
|
4
|
+
|
|
5
|
+
## Declare Return Types for Top-Level Functions
|
|
6
|
+
|
|
7
|
+
> Source: [Should You Declare Return Types?](https://www.totaltypescript.com/should-you-declare-return-types)
|
|
8
|
+
|
|
9
|
+
Declare return types on module-level functions. This communicates intent and helps both humans and AI tools understand what a function is designed to return.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Do this for top-level functions
|
|
13
|
+
const fetchUser = async (id: string): Promise<User> => {
|
|
14
|
+
const res = await fetch(`/api/users/${id}`);
|
|
15
|
+
return res.json();
|
|
16
|
+
};
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Exception:** Skip return types for components returning JSX — the return type is always obvious.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Skip for JSX-returning components
|
|
23
|
+
const Avatar = (props: AvatarProps) => {
|
|
24
|
+
return <img alt={props.name} />;
|
|
25
|
+
};
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Prefer `const` Over `let`
|
|
29
|
+
|
|
30
|
+
> Source: [How let and const Work In TypeScript](https://www.totaltypescript.com/let-and-const)
|
|
31
|
+
|
|
32
|
+
`const` gives TypeScript narrower, literal type inference. `let` widens to the base type because the variable could be reassigned.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const genre = "rock"; // type: "rock" (literal)
|
|
36
|
+
let genre = "rock"; // type: string (widened)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Prefer `const` to get stricter types. If you must use `let`, add an explicit type annotation.
|
|
40
|
+
|
|
41
|
+
## Prefer `T[]` Over `Array<T>`
|
|
42
|
+
|
|
43
|
+
> Source: [Array\<T\> vs T[]: Which is better?](https://www.totaltypescript.com/array-types-in-typescript)
|
|
44
|
+
|
|
45
|
+
Both are functionally identical. Matt prefers `T[]` because:
|
|
46
|
+
- TypeScript's compiler, hover info, and error messages always use `T[]` syntax
|
|
47
|
+
- It feels more natural and is used throughout the official docs
|
|
48
|
+
|
|
49
|
+
**Caveat:** `Array<keyof Person>` avoids the need for parentheses that `(keyof Person)[]` requires. Minor edge case.
|
|
50
|
+
|
|
51
|
+
**What matters most:** Pick one and enforce it with an ESLint rule. Consistency beats preference.
|
|
52
|
+
|
|
53
|
+
## Don't Use Method Shorthand Syntax
|
|
54
|
+
|
|
55
|
+
> Source: [Method Shorthand Syntax Considered Harmful](https://www.totaltypescript.com/method-shorthand-syntax-considered-harmful)
|
|
56
|
+
|
|
57
|
+
Method shorthand in interfaces/types is **bivariant** — it accepts parameter types both narrower and wider than specified, creating a type safety gap.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Bivariant (unsafe) — allows narrowing the parameter
|
|
61
|
+
interface Handler {
|
|
62
|
+
handle(input: Animal): void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Properly covariant (safe)
|
|
66
|
+
interface Handler {
|
|
67
|
+
handle: (input: Animal) => void;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
With the shorthand, you can implement `handle` expecting `Dog` instead of `Animal`, then pass a `Cat` at runtime — TypeScript won't catch it.
|
|
72
|
+
|
|
73
|
+
**Enforcement:** Use the ESLint rule `@typescript-eslint/method-signature-style` with `"property"` option.
|
|
74
|
+
|
|
75
|
+
## Never Use the `Function` Type
|
|
76
|
+
|
|
77
|
+
> Source: [Don't use Function type in TypeScript](https://www.totaltypescript.com/dont-use-function-keyword-in-typescript)
|
|
78
|
+
|
|
79
|
+
`Function` represents *any* function with no type information. It kills inference, autocomplete, and safety.
|
|
80
|
+
|
|
81
|
+
| Instead of | Use |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `Function` | `(...args: any[]) => any` (any function) |
|
|
84
|
+
| `Function` | `() => void` (no-arg callback) |
|
|
85
|
+
| `Function` | `(item: T) => number` (specific signature) |
|
|
86
|
+
|
|
87
|
+
Be as specific as possible. The more precise the function type, the more TypeScript can help you.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Runtime Validation
|
|
2
|
+
|
|
3
|
+
> Source: [When Should You Use Zod?](https://www.totaltypescript.com/when-should-you-use-zod) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## The Framework: Trust Boundaries
|
|
6
|
+
|
|
7
|
+
Matt's recommendation is based on how much you trust the data entering your application.
|
|
8
|
+
|
|
9
|
+
## Use Zod: Untrusted Inputs
|
|
10
|
+
|
|
11
|
+
Validate data you don't control. These are attack vectors and sources of runtime surprises:
|
|
12
|
+
|
|
13
|
+
- **CLI arguments** (`process.argv`)
|
|
14
|
+
- **Public API endpoints** (path params, headers, request bodies)
|
|
15
|
+
- **Form submissions**
|
|
16
|
+
- **WebSocket connections**
|
|
17
|
+
- **`localStorage`** (users can manipulate it directly)
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
const CreateUserSchema = z.object({
|
|
23
|
+
name: z.string().min(1),
|
|
24
|
+
email: z.string().email(),
|
|
25
|
+
age: z.number().int().positive(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// At the API boundary
|
|
29
|
+
const result = CreateUserSchema.safeParse(req.body);
|
|
30
|
+
if (!result.success) {
|
|
31
|
+
return res.status(400).json(result.error);
|
|
32
|
+
}
|
|
33
|
+
// result.data is now fully typed and validated
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
As Matt notes: "If these APIs are exposed to the world, anyone can ping them. If they aren't validated, many might even be vectors for attack."
|
|
37
|
+
|
|
38
|
+
## Consider Zod: Third-Party APIs
|
|
39
|
+
|
|
40
|
+
APIs you don't control can change without warning. Validation at the boundary catches drift early:
|
|
41
|
+
|
|
42
|
+
> "Validation will be thrown early, right when the data enters your app. This makes it much easier to debug and fix."
|
|
43
|
+
|
|
44
|
+
**Tradeoff:** Zod adds ~12kb gzipped. Consider whether that matters for your bundle.
|
|
45
|
+
|
|
46
|
+
## Skip Zod: Controlled Inputs
|
|
47
|
+
|
|
48
|
+
When you control both ends of the data flow (e.g., fullstack app where your frontend talks to your own backend):
|
|
49
|
+
|
|
50
|
+
- Version drift is "usually just a browser refresh away from a better experience"
|
|
51
|
+
- The security risk is minimal since you control the server
|
|
52
|
+
- The overhead of maintaining schemas for internal data isn't worth it
|
|
53
|
+
|
|
54
|
+
## Summary
|
|
55
|
+
|
|
56
|
+
| Data Source | Trust Level | Validate? |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| User input (forms, CLI, localStorage) | None | Yes |
|
|
59
|
+
| Public API endpoints | None | Yes |
|
|
60
|
+
| Third-party APIs | Low | Recommended |
|
|
61
|
+
| Your own backend (same deploy) | High | Usually skip |
|
|
62
|
+
| Internal function calls | Full | Never |
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# The `satisfies` Operator
|
|
2
|
+
|
|
3
|
+
> Source: [5 Ways to Use Satisfies in TypeScript](https://www.totaltypescript.com/how-to-use-satisfies-operator) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## What It Does
|
|
6
|
+
|
|
7
|
+
`satisfies` validates that a value conforms to a type **without widening the inferred type**. You get compile-time validation and narrow inference at the same time.
|
|
8
|
+
|
|
9
|
+
## 5 Practical Uses
|
|
10
|
+
|
|
11
|
+
### 1. Strongly Typed URL Search Parameters
|
|
12
|
+
|
|
13
|
+
`URLSearchParams` normally accepts loose `Record<string, string>`. Use `satisfies` to catch missing required fields:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
type GHIssueURLParams = {
|
|
17
|
+
title: string;
|
|
18
|
+
body: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const params = new URLSearchParams({
|
|
22
|
+
title: "New Issue",
|
|
23
|
+
} satisfies GHIssueURLParams);
|
|
24
|
+
// Error: Property 'body' is missing
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Strongly Typed POST Request Bodies
|
|
28
|
+
|
|
29
|
+
`JSON.stringify` erases type information. Validate the body shape before serialization:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
type Post = {
|
|
33
|
+
title: string;
|
|
34
|
+
content: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
fetch("/api/posts", {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: JSON.stringify({
|
|
40
|
+
title: "New Post",
|
|
41
|
+
content: "Lorem ipsum.",
|
|
42
|
+
} satisfies Post),
|
|
43
|
+
});
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Inferring Tuples Without `as const`
|
|
47
|
+
|
|
48
|
+
Get tuple behavior with index bounds checking:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
type MoreThanOneMember = [any, ...any[]];
|
|
52
|
+
|
|
53
|
+
const tuple = [1, 2, 3] satisfies MoreThanOneMember;
|
|
54
|
+
const doesNotExist = tuple[3]; // Error: index out of bounds
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 4. Enforcing `as const` Object Shapes
|
|
58
|
+
|
|
59
|
+
Combine `as const` with `satisfies` to get immutable objects that must conform to a shape:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
type RouteConfig = Record<
|
|
63
|
+
string,
|
|
64
|
+
{ url: string; searchParams: Record<string, string> }
|
|
65
|
+
>;
|
|
66
|
+
|
|
67
|
+
const routes = {
|
|
68
|
+
home: { url: "/", searchParams: {} },
|
|
69
|
+
about: { url: "/about", searchParams: {} },
|
|
70
|
+
} as const satisfies RouteConfig;
|
|
71
|
+
|
|
72
|
+
// routes.home.url is narrowed to "/" (not string)
|
|
73
|
+
// AND the shape is validated against RouteConfig
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 5. Enforcing `as const` Array Shapes
|
|
77
|
+
|
|
78
|
+
Validate recursive structures while preserving literal types:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
type NavElement = {
|
|
82
|
+
title: string;
|
|
83
|
+
url?: string;
|
|
84
|
+
children?: readonly NavElement[];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const nav = [
|
|
88
|
+
{ title: "Home", url: "/" },
|
|
89
|
+
{
|
|
90
|
+
title: "About",
|
|
91
|
+
children: [{ title: "Team", url: "/about/team" }],
|
|
92
|
+
},
|
|
93
|
+
] as const satisfies readonly NavElement[];
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Important:** Array properties in the type must be marked `readonly` to align with `as const`'s immutability.
|
|
97
|
+
|
|
98
|
+
## When to Reach for `satisfies`
|
|
99
|
+
|
|
100
|
+
Use it when you need **both** validation against a type **and** narrow inference of the actual value. If you only need one or the other, a regular type annotation or `as const` alone is sufficient.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# TSConfig Cheat Sheet
|
|
2
|
+
|
|
3
|
+
> Source: [The TSConfig Cheat Sheet](https://www.totaltypescript.com/tsconfig-cheat-sheet) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## Base Options (Every Project)
|
|
6
|
+
|
|
7
|
+
These belong in every `tsconfig.json`:
|
|
8
|
+
|
|
9
|
+
```jsonc
|
|
10
|
+
{
|
|
11
|
+
"compilerOptions": {
|
|
12
|
+
// Smooth over CJS/ESM interop differences
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
|
|
15
|
+
// Skip type-checking node_modules .d.ts files for performance
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
|
|
18
|
+
// Stable target — prefer over "esnext"
|
|
19
|
+
"target": "es2022",
|
|
20
|
+
|
|
21
|
+
// Allow importing .js and .json files
|
|
22
|
+
"allowJs": true,
|
|
23
|
+
"resolveJsonModule": true,
|
|
24
|
+
|
|
25
|
+
// Treat all files as modules (avoids redeclaration errors)
|
|
26
|
+
"moduleDetection": "force",
|
|
27
|
+
|
|
28
|
+
// Prevent unsafe TS features that break per-file transpilation
|
|
29
|
+
"isolatedModules": true,
|
|
30
|
+
|
|
31
|
+
// Enforce import type / export type syntax
|
|
32
|
+
"verbatimModuleSyntax": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Strictness
|
|
38
|
+
|
|
39
|
+
Enable all strict checks, plus two extras Matt considers essential:
|
|
40
|
+
|
|
41
|
+
```jsonc
|
|
42
|
+
{
|
|
43
|
+
"compilerOptions": {
|
|
44
|
+
// All strict type-checking options
|
|
45
|
+
"strict": true,
|
|
46
|
+
|
|
47
|
+
// Force checking array/object index access (prevents undefined bugs)
|
|
48
|
+
"noUncheckedIndexedAccess": true,
|
|
49
|
+
|
|
50
|
+
// Make the override keyword functional in classes
|
|
51
|
+
"noImplicitOverride": true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Matt **deliberately excludes** noisier options like `noImplicitReturns`, `noUnusedLocals`, and `noUnusedParameters`. Add them only if your team wants them — they create friction during development.
|
|
57
|
+
|
|
58
|
+
## When TypeScript Transpiles (tsc emits JS)
|
|
59
|
+
|
|
60
|
+
```jsonc
|
|
61
|
+
{
|
|
62
|
+
"compilerOptions": {
|
|
63
|
+
"module": "NodeNext",
|
|
64
|
+
"outDir": "dist"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### For Published Libraries
|
|
70
|
+
|
|
71
|
+
Add `.d.ts` generation so consumers get autocomplete:
|
|
72
|
+
|
|
73
|
+
```jsonc
|
|
74
|
+
{
|
|
75
|
+
"compilerOptions": {
|
|
76
|
+
"declaration": true
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### For Monorepo Packages
|
|
82
|
+
|
|
83
|
+
```jsonc
|
|
84
|
+
{
|
|
85
|
+
"compilerOptions": {
|
|
86
|
+
"composite": true,
|
|
87
|
+
"sourceMap": true,
|
|
88
|
+
"declarationMap": true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## When TypeScript Does NOT Transpile (Linting Mode)
|
|
94
|
+
|
|
95
|
+
When a bundler (Vite, esbuild, etc.) handles transpilation:
|
|
96
|
+
|
|
97
|
+
```jsonc
|
|
98
|
+
{
|
|
99
|
+
"compilerOptions": {
|
|
100
|
+
// Mimic bundler module resolution
|
|
101
|
+
"module": "preserve",
|
|
102
|
+
|
|
103
|
+
// Don't emit JS files
|
|
104
|
+
"noEmit": true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Environment-Specific `lib`
|
|
110
|
+
|
|
111
|
+
**DOM environments** (browsers, SSR with DOM APIs):
|
|
112
|
+
|
|
113
|
+
```jsonc
|
|
114
|
+
{
|
|
115
|
+
"compilerOptions": {
|
|
116
|
+
"lib": ["es2022", "dom", "dom.iterable"]
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Non-DOM environments** (Node.js CLIs, serverless, workers):
|
|
122
|
+
|
|
123
|
+
```jsonc
|
|
124
|
+
{
|
|
125
|
+
"compilerOptions": {
|
|
126
|
+
"lib": ["es2022"]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Type Organization
|
|
2
|
+
|
|
3
|
+
> Source: [Where To Put Your Types in Application Code](https://www.totaltypescript.com/where-to-put-your-types-in-application-code) — Matt Pocock
|
|
4
|
+
|
|
5
|
+
## Three Rules
|
|
6
|
+
|
|
7
|
+
### Rule 1: Colocate Single-Use Types
|
|
8
|
+
|
|
9
|
+
When a type is used in only one place, put it in the same file where it's used.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// components/Avatar.tsx
|
|
13
|
+
type AvatarProps = {
|
|
14
|
+
imageUrl: string;
|
|
15
|
+
name: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function Avatar({ imageUrl, name }: AvatarProps) {
|
|
19
|
+
// ...
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Don't extract single-use types into separate files. It's low-cost to refactor later if the type grows in scope. Inlining is fine for truly single-use types.
|
|
24
|
+
|
|
25
|
+
### Rule 2: Share Types Across Modules
|
|
26
|
+
|
|
27
|
+
When a type serves multiple files, move it to a shared `*.types.ts` file at the appropriate level:
|
|
28
|
+
|
|
29
|
+
- **App-wide types** → `src/types.ts` or `src/shared.types.ts`
|
|
30
|
+
- **Folder-specific types** → `src/features/auth/auth.types.ts`
|
|
31
|
+
|
|
32
|
+
This mirrors how you'd handle shared functions — keep them at the narrowest scope that covers all consumers.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
src/
|
|
36
|
+
├── types.ts # App-wide types
|
|
37
|
+
├── features/
|
|
38
|
+
│ ├── auth/
|
|
39
|
+
│ │ ├── auth.types.ts # Shared within auth feature
|
|
40
|
+
│ │ ├── login.tsx
|
|
41
|
+
│ │ └── signup.tsx
|
|
42
|
+
│ └── billing/
|
|
43
|
+
│ ├── billing.types.ts
|
|
44
|
+
│ └── invoice.tsx
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Rule 3: Monorepo-Level Sharing
|
|
48
|
+
|
|
49
|
+
In a monorepo, types used across multiple packages go in a dedicated shared package:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
packages/
|
|
53
|
+
├── types/ # Shared types package
|
|
54
|
+
│ └── src/
|
|
55
|
+
│ └── index.ts
|
|
56
|
+
├── web-app/
|
|
57
|
+
│ └── src/
|
|
58
|
+
└── api/
|
|
59
|
+
└── src/
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Core Principle
|
|
63
|
+
|
|
64
|
+
Share types across the **smallest number of modules** that require them. Don't pre-extract types "just in case" — colocate by default and widen scope only when a second consumer appears.
|