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.
Files changed (68) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +54 -13
  3. package/bin/cli.ts +75 -19
  4. package/package.json +6 -7
  5. package/src/generator.ts +64 -26
  6. package/src/refresh-hint.ts +95 -0
  7. package/bin/postinstall.mjs +0 -69
  8. package/docs/README.md +0 -59
  9. package/docs/agents/code-reviewer.md +0 -76
  10. package/docs/agents/github-workflow.md +0 -98
  11. package/docs/best-practices/capacitor/README.md +0 -64
  12. package/docs/best-practices/capacitor/capacitor-8.md +0 -60
  13. package/docs/best-practices/capacitor/capgo-setup.md +0 -151
  14. package/docs/best-practices/capacitor/channels-and-rollouts.md +0 -101
  15. package/docs/best-practices/capacitor/live-updates-ota.md +0 -75
  16. package/docs/best-practices/capacitor/security-encryption.md +0 -68
  17. package/docs/best-practices/solidjs/README.md +0 -43
  18. package/docs/best-practices/solidjs/anti-patterns.md +0 -166
  19. package/docs/best-practices/solidjs/component-patterns.md +0 -131
  20. package/docs/best-practices/solidjs/context-and-global-state.md +0 -131
  21. package/docs/best-practices/solidjs/control-flow.md +0 -124
  22. package/docs/best-practices/solidjs/data-fetching.md +0 -205
  23. package/docs/best-practices/solidjs/effects-and-lifecycle.md +0 -113
  24. package/docs/best-practices/solidjs/performance.md +0 -100
  25. package/docs/best-practices/solidjs/props-patterns.md +0 -100
  26. package/docs/best-practices/solidjs/reactivity-model.md +0 -104
  27. package/docs/best-practices/solidjs/signals-and-state.md +0 -78
  28. package/docs/best-practices/solidjs/stores-and-nested-state.md +0 -111
  29. package/docs/best-practices/solidjs/typescript-integration.md +0 -186
  30. package/docs/best-practices/testing/README.md +0 -84
  31. package/docs/best-practices/testing/playwright-e2e.md +0 -649
  32. package/docs/best-practices/testing/storybook-interaction.md +0 -455
  33. package/docs/best-practices/testing/vitest-unit.md +0 -451
  34. package/docs/best-practices/typescript/README.md +0 -45
  35. package/docs/best-practices/typescript/any-and-unknown.md +0 -73
  36. package/docs/best-practices/typescript/deriving-vs-decoupling.md +0 -83
  37. package/docs/best-practices/typescript/discriminated-unions.md +0 -75
  38. package/docs/best-practices/typescript/enums-alternatives.md +0 -72
  39. package/docs/best-practices/typescript/essential-patterns.md +0 -119
  40. package/docs/best-practices/typescript/generics-patterns.md +0 -105
  41. package/docs/best-practices/typescript/micro-opinions.md +0 -87
  42. package/docs/best-practices/typescript/runtime-validation.md +0 -62
  43. package/docs/best-practices/typescript/satisfies-operator.md +0 -100
  44. package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +0 -160
  45. package/docs/best-practices/typescript/type-organization.md +0 -64
  46. package/docs/best-practices/typescript/type-vs-interface.md +0 -80
  47. package/docs/commands/code-quality.md +0 -36
  48. package/docs/commands/onboard.md +0 -63
  49. package/docs/commands/pr-review.md +0 -91
  50. package/docs/commands/pr-summary.md +0 -50
  51. package/docs/commands/proto-check.md +0 -59
  52. package/docs/commands/ticket.md +0 -56
  53. package/docs/skills/code-style.md +0 -26
  54. package/docs/skills/systematic-debugging.md +0 -70
  55. package/docs/skills/testing-patterns.md +0 -140
  56. package/docs/skills/typescript-conventions.md +0 -283
  57. package/docs/skills/verification-before-completion.md +0 -103
  58. package/docs/stacks/capacitor-ota.md +0 -145
  59. package/docs/stacks/cloudflare-d1-kv.md +0 -144
  60. package/docs/stacks/esnext-idioms.md +0 -25
  61. package/docs/stacks/i18n-typesafe.md +0 -141
  62. package/docs/stacks/playwright-patterns.md +0 -179
  63. package/docs/stacks/protobuf-contracts.md +0 -85
  64. package/docs/stacks/rust-wasm-patterns.md +0 -106
  65. package/docs/stacks/solidjs-patterns.md +0 -110
  66. package/docs/stacks/storybook-patterns.md +0 -177
  67. package/docs/stacks/vanilla-extract-patterns.md +0 -115
  68. 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.