claude-toolkit 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/README.md +54 -13
- package/bin/cli.ts +75 -19
- package/package.json +6 -7
- package/src/generator.ts +64 -26
- package/src/refresh-hint.ts +95 -0
- package/bin/postinstall.mjs +0 -69
- package/docs/README.md +0 -59
- package/docs/agents/code-reviewer.md +0 -76
- package/docs/agents/github-workflow.md +0 -98
- package/docs/best-practices/capacitor/README.md +0 -64
- package/docs/best-practices/capacitor/capacitor-8.md +0 -60
- package/docs/best-practices/capacitor/capgo-setup.md +0 -151
- package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
- package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
- package/docs/best-practices/capacitor/security-encryption.md +0 -68
- package/docs/best-practices/solidjs/README.md +0 -43
- package/docs/best-practices/solidjs/anti-patterns.md +0 -166
- package/docs/best-practices/solidjs/component-patterns.md +0 -131
- package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
- package/docs/best-practices/solidjs/control-flow.md +0 -124
- package/docs/best-practices/solidjs/data-fetching.md +0 -205
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
- package/docs/best-practices/solidjs/performance.md +0 -100
- package/docs/best-practices/solidjs/props-patterns.md +0 -100
- package/docs/best-practices/solidjs/reactivity-model.md +0 -104
- package/docs/best-practices/solidjs/signals-and-state.md +0 -78
- package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
- package/docs/best-practices/solidjs/typescript-integration.md +0 -186
- package/docs/best-practices/testing/README.md +0 -84
- package/docs/best-practices/testing/playwright-e2e.md +0 -649
- package/docs/best-practices/testing/storybook-interaction.md +0 -455
- package/docs/best-practices/testing/vitest-unit.md +0 -451
- package/docs/best-practices/typescript/README.md +0 -45
- package/docs/best-practices/typescript/any-and-unknown.md +0 -73
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
- package/docs/best-practices/typescript/discriminated-unions.md +0 -75
- package/docs/best-practices/typescript/enums-alternatives.md +0 -72
- package/docs/best-practices/typescript/essential-patterns.md +0 -119
- package/docs/best-practices/typescript/generics-patterns.md +0 -105
- package/docs/best-practices/typescript/micro-opinions.md +0 -87
- package/docs/best-practices/typescript/runtime-validation.md +0 -62
- package/docs/best-practices/typescript/satisfies-operator.md +0 -100
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
- package/docs/best-practices/typescript/type-organization.md +0 -64
- package/docs/best-practices/typescript/type-vs-interface.md +0 -80
- package/docs/commands/code-quality.md +0 -36
- package/docs/commands/onboard.md +0 -63
- package/docs/commands/pr-review.md +0 -91
- package/docs/commands/pr-summary.md +0 -50
- package/docs/commands/proto-check.md +0 -59
- package/docs/commands/ticket.md +0 -56
- package/docs/skills/code-style.md +0 -26
- package/docs/skills/systematic-debugging.md +0 -70
- package/docs/skills/testing-patterns.md +0 -140
- package/docs/skills/typescript-conventions.md +0 -283
- package/docs/skills/verification-before-completion.md +0 -103
- package/docs/stacks/capacitor-ota.md +0 -145
- package/docs/stacks/cloudflare-d1-kv.md +0 -144
- package/docs/stacks/esnext-idioms.md +0 -25
- package/docs/stacks/i18n-typesafe.md +0 -141
- package/docs/stacks/playwright-patterns.md +0 -179
- package/docs/stacks/protobuf-contracts.md +0 -85
- package/docs/stacks/rust-wasm-patterns.md +0 -106
- package/docs/stacks/solidjs-patterns.md +0 -110
- package/docs/stacks/storybook-patterns.md +0 -177
- package/docs/stacks/vanilla-extract-patterns.md +0 -115
- package/docs/stacks/vite-vitest-patterns.md +0 -485
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# Discriminated Unions
|
|
2
|
-
|
|
3
|
-
> Source: [TypeScript Discriminated Unions for Frontend Developers](https://www.totaltypescript.com/discriminated-unions-are-a-devs-best-friend) — Matt Pocock
|
|
4
|
-
|
|
5
|
-
## The Problem: Bag of Optionals
|
|
6
|
-
|
|
7
|
-
The naive approach to modeling state uses optional properties in a single type:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// Don't do this
|
|
11
|
-
type State = {
|
|
12
|
-
status: "loading" | "success" | "error";
|
|
13
|
-
data?: { id: string };
|
|
14
|
-
error?: Error;
|
|
15
|
-
};
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
This allows **impossible states**: you can have `status: "success"` with no `data`, or `status: "loading"` with an `error`. TypeScript can't help you because every property is independently optional.
|
|
19
|
-
|
|
20
|
-
## The Solution: Discriminated Unions
|
|
21
|
-
|
|
22
|
-
Model each state as a distinct member of a union, with only the properties relevant to that state:
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
type State =
|
|
26
|
-
| { status: "loading" }
|
|
27
|
-
| { status: "success"; data: { id: string } }
|
|
28
|
-
| { status: "error"; error: Error };
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Now `data` only exists when `status` is `"success"`, and `error` only exists when `status` is `"error"`. Impossible states are unrepresentable.
|
|
32
|
-
|
|
33
|
-
## Type Narrowing
|
|
34
|
-
|
|
35
|
-
You must check the discriminator before accessing state-specific properties. This strictness is a feature:
|
|
36
|
-
|
|
37
|
-
```typescript
|
|
38
|
-
function handleState(state: State) {
|
|
39
|
-
if (state.status === "success") {
|
|
40
|
-
// TypeScript knows state.data exists here
|
|
41
|
-
console.log(state.data.id);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (state.status === "error") {
|
|
45
|
-
// TypeScript knows state.error exists here
|
|
46
|
-
console.log(state.error.message);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Component Props Pattern
|
|
52
|
-
|
|
53
|
-
Apply discriminated unions to component APIs with conditional properties:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
type ModalProps =
|
|
57
|
-
| {
|
|
58
|
-
variant: "with-description";
|
|
59
|
-
title: string;
|
|
60
|
-
description: string;
|
|
61
|
-
buttonText: string;
|
|
62
|
-
}
|
|
63
|
-
| {
|
|
64
|
-
variant: "base";
|
|
65
|
-
title: string;
|
|
66
|
-
};
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
This prevents consumers from passing `description` without `buttonText`, or using the wrong combination of props.
|
|
70
|
-
|
|
71
|
-
## Why This Matters
|
|
72
|
-
|
|
73
|
-
As Matt puts it: "Instead of a big optional bag of data, you'll start understanding the connections between data and UI."
|
|
74
|
-
|
|
75
|
-
Discriminated unions force you to think about which data belongs together in which state — and the compiler enforces those relationships for you.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Enums & Alternatives
|
|
2
|
-
|
|
3
|
-
> Source: [Why I Don't Like Enums](https://www.totaltypescript.com/why-i-dont-like-typescript-enums) — Matt Pocock
|
|
4
|
-
|
|
5
|
-
## The Verdict
|
|
6
|
-
|
|
7
|
-
**Avoid enums. Use `as const` objects with derived union types instead.**
|
|
8
|
-
|
|
9
|
-
## Why Enums Are Problematic
|
|
10
|
-
|
|
11
|
-
### 1. Numeric Enums Accept Raw Numbers
|
|
12
|
-
|
|
13
|
-
Numeric enums allow passing any raw number where an enum value is expected — completely defeating the purpose of type safety.
|
|
14
|
-
|
|
15
|
-
```typescript
|
|
16
|
-
enum Direction {
|
|
17
|
-
Up, // 0
|
|
18
|
-
Down, // 1
|
|
19
|
-
Left, // 2
|
|
20
|
-
Right, // 3
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function move(dir: Direction) {}
|
|
24
|
-
|
|
25
|
-
move(99); // No error! TypeScript allows this.
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### 2. Inconsistent Behavior
|
|
29
|
-
|
|
30
|
-
Numeric and string enums behave differently:
|
|
31
|
-
- Numeric enums generate **bidirectional** mappings (value-to-key and key-to-value), doubling the runtime keys
|
|
32
|
-
- String enums enforce stricter type-checking but don't generate reverse mappings
|
|
33
|
-
- You can mix numeric and string values in a single enum (don't)
|
|
34
|
-
|
|
35
|
-
### 3. Nominal Typing
|
|
36
|
-
|
|
37
|
-
Enums use nominal typing — two enums with identical values are not interchangeable. This breaks from JavaScript's structural approach and creates surprising incompatibilities.
|
|
38
|
-
|
|
39
|
-
### 4. Implementation Bugs
|
|
40
|
-
|
|
41
|
-
The TypeScript repository has 71 open enum-related bug issues. The TS team has indicated many are architecturally unfixable.
|
|
42
|
-
|
|
43
|
-
## The Alternative: `as const` Objects
|
|
44
|
-
|
|
45
|
-
```typescript
|
|
46
|
-
const Direction = {
|
|
47
|
-
Up: "up",
|
|
48
|
-
Down: "down",
|
|
49
|
-
Left: "left",
|
|
50
|
-
Right: "right",
|
|
51
|
-
} as const;
|
|
52
|
-
|
|
53
|
-
type Direction = (typeof Direction)[keyof typeof Direction];
|
|
54
|
-
// "up" | "down" | "left" | "right"
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
This gives you:
|
|
58
|
-
- A runtime object for lookups (`Direction.Up`)
|
|
59
|
-
- A union type for type safety (`Direction`)
|
|
60
|
-
- No surprising behavior
|
|
61
|
-
- Standard JavaScript semantics
|
|
62
|
-
|
|
63
|
-
## If You Must Use Enums
|
|
64
|
-
|
|
65
|
-
Use **string enums only**. They behave more predictably, resemble the transpiled output more closely, and don't allow raw value assignment.
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
enum Status {
|
|
69
|
-
Active = "active",
|
|
70
|
-
Inactive = "inactive",
|
|
71
|
-
}
|
|
72
|
-
```
|
|
@@ -1,119 +0,0 @@
|
|
|
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.
|
|
@@ -1,105 +0,0 @@
|
|
|
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.
|
|
@@ -1,87 +0,0 @@
|
|
|
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.
|
|
@@ -1,62 +0,0 @@
|
|
|
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 |
|
|
@@ -1,100 +0,0 @@
|
|
|
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.
|