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,451 +0,0 @@
1
- # Vitest Unit Testing
2
-
3
- > Sources: [Vitest 4.0 Announcement](https://voidzero.dev/posts/announcing-vitest-4), [Vitest 4.1 Blog](https://main.vitest.dev/blog/vitest-4-1), [Vitest Docs](https://vitest.dev/), [SolidJS Testing Docs](https://docs.solidjs.com/guides/testing), [@solidjs/testing-library](https://github.com/solidjs/solid-testing-library)
4
-
5
- Unit tests form the base of the testing pyramid. They are fast, isolated, and cover pure logic, reactive primitives, and component behavior in a JSDOM environment.
6
-
7
- ## AAA Pattern
8
-
9
- Every test follows **Arrange-Act-Assert**. Keep each phase visually distinct:
10
-
11
- ```typescript
12
- it("should format currency correctly", () => {
13
- // Arrange
14
- const amount = 1234.5;
15
-
16
- // Act
17
- const result = formatCurrency(amount, "NZD");
18
-
19
- // Assert
20
- expect(result).toBe("$1,234.50");
21
- });
22
- ```
23
-
24
- For simple tests, the phases can be implicit. For complex tests, add blank lines between them.
25
-
26
- ## Naming Conventions
27
-
28
- - **Files**: `*.test.ts` / `*.test.tsx`, co-located with source or in `tests/`
29
- - **Describe blocks**: Name the unit under test (`describe("formatCurrency", ...)`)
30
- - **It blocks**: Describe behavior, not implementation ("should return loading state when resource is pending", not "should set isLoading to true")
31
-
32
- ## Testing SolidJS Components
33
-
34
- ### render() Takes a Callback
35
-
36
- This is different from React Testing Library. SolidJS needs the callback form to establish a reactive root:
37
-
38
- ```tsx
39
- import { render, screen } from "@solidjs/testing-library";
40
- import { Button } from "../src/components/Button";
41
-
42
- it("should render button text", () => {
43
- render(() => <Button label="Click me" />);
44
- expect(screen.getByRole("button")).toHaveTextContent("Click me");
45
- });
46
- ```
47
-
48
- ### Testing Signals with renderHook
49
-
50
- For hooks/composables that return reactive state:
51
-
52
- ```typescript
53
- import { renderHook } from "@solidjs/testing-library";
54
- import { useCounter } from "../src/hooks/useCounter";
55
-
56
- it("should increment counter", () => {
57
- const { result } = renderHook(useCounter);
58
- expect(result.count).toBe(0);
59
- result.increment();
60
- expect(result.count).toBe(1);
61
- });
62
- ```
63
-
64
- ### Testing Signals with createRoot
65
-
66
- For primitives that don't need component context, `createRoot` is sufficient and faster:
67
-
68
- ```typescript
69
- import { createRoot, createSignal, createMemo } from "solid-js";
70
-
71
- it("should compute derived value", () => {
72
- createRoot((dispose) => {
73
- const [count, setCount] = createSignal(0);
74
- const doubled = createMemo(() => count() * 2);
75
-
76
- expect(doubled()).toBe(0);
77
- setCount(5);
78
- expect(doubled()).toBe(10);
79
-
80
- dispose();
81
- });
82
- });
83
- ```
84
-
85
- ### Testing Effects with testEffect
86
-
87
- Effects are async in SolidJS. The `testEffect` utility provides a `done` callback for async assertion:
88
-
89
- ```typescript
90
- import { testEffect } from "@solidjs/testing-library";
91
- import { createSignal, createEffect } from "solid-js";
92
-
93
- it("should react to signal changes", () => {
94
- const [value, setValue] = createSignal(0);
95
-
96
- return testEffect((done) =>
97
- createEffect((run: number = 0) => {
98
- if (run === 0) {
99
- expect(value()).toBe(0);
100
- setValue(1);
101
- } else if (run === 1) {
102
- expect(value()).toBe(1);
103
- done();
104
- }
105
- return run + 1;
106
- })
107
- );
108
- });
109
- ```
110
-
111
- ### Testing Resources (createResource)
112
-
113
- Resources trigger Suspense. Wrap in `<Suspense>` and assert loading/data/error states:
114
-
115
- ```tsx
116
- import { render, screen } from "@solidjs/testing-library";
117
- import { Suspense } from "solid-js";
118
-
119
- it("should show loading then data", async () => {
120
- render(() => (
121
- <Suspense fallback={<div>Loading...</div>}>
122
- <MyResourceComponent />
123
- </Suspense>
124
- ));
125
-
126
- expect(screen.getByText("Loading...")).toBeInTheDocument();
127
- expect(await screen.findByText("Data loaded")).toBeInTheDocument();
128
- });
129
- ```
130
-
131
- ## Mocking Strategies
132
-
133
- ### Decision Framework
134
-
135
- | Scenario | Tool |
136
- |---|---|
137
- | Replace entire module | `vi.mock("module")` |
138
- | Spy on a specific method | `vi.spyOn(obj, "method")` |
139
- | Network requests | MSW (`msw/node`) |
140
- | Vary mock per test | `vi.hoisted` + `mockReturnValue` |
141
- | Non-hoisted dynamic mock | `vi.doMock` |
142
-
143
- ### vi.mock with vi.hoisted (Recommended Pattern)
144
-
145
- `vi.hoisted` lets you declare mock references that are accessible inside `vi.mock` (which is hoisted to the top of the file):
146
-
147
- ```typescript
148
- const { mockFetch } = vi.hoisted(() => ({
149
- mockFetch: vi.fn(),
150
- }));
151
-
152
- vi.mock("../src/api/client", () => ({
153
- fetchData: mockFetch,
154
- }));
155
-
156
- it("should handle API error", () => {
157
- mockFetch.mockRejectedValue(new Error("Network error"));
158
- // ... test
159
- });
160
- ```
161
-
162
- ### vi.spyOn for Partial Mocking
163
-
164
- When you only need to intercept one method while keeping the rest real:
165
-
166
- ```typescript
167
- import * as utils from "../src/utils";
168
-
169
- it("should call logger", () => {
170
- const spy = vi.spyOn(utils, "log");
171
- doSomething();
172
- expect(spy).toHaveBeenCalledWith("action completed");
173
- spy.mockRestore();
174
- });
175
- ```
176
-
177
- ### MSW for Network Mocking
178
-
179
- Mock Service Worker intercepts at the network level, giving realistic integration tests:
180
-
181
- ```typescript
182
- import { setupServer } from "msw/node";
183
- import { http, HttpResponse } from "msw";
184
-
185
- const server = setupServer(
186
- http.get("/api/user", () => HttpResponse.json({ name: "Alice" }))
187
- );
188
-
189
- beforeAll(() => server.listen());
190
- afterEach(() => server.resetHandlers());
191
- afterAll(() => server.close());
192
-
193
- it("should display user name", async () => {
194
- render(() => <UserProfile />);
195
- expect(await screen.findByText("Alice")).toBeInTheDocument();
196
- });
197
- ```
198
-
199
- ### mockThrow / mockThrowOnce (Vitest 4.1+)
200
-
201
- Concise error path testing without wrapping in functions:
202
-
203
- ```typescript
204
- mockFn.mockThrow(new Error("failed"));
205
- mockFn.mockThrowOnce(new Error("first call fails"));
206
- ```
207
-
208
- ### Type-Safe Mocks
209
-
210
- Use `vi.mocked()` for typed access to mocked functions:
211
-
212
- ```typescript
213
- import { fetchUser } from "../src/api";
214
- vi.mock("../src/api");
215
-
216
- const mockedFetchUser = vi.mocked(fetchUser);
217
- mockedFetchUser.mockResolvedValue({ name: "Alice" });
218
- ```
219
-
220
- ## Snapshot Testing
221
-
222
- ### When to Use
223
-
224
- - Serialized objects, API response shapes, configuration objects
225
- - Small component output (buttons, cards, form fields)
226
-
227
- ### When NOT to Use
228
-
229
- - Large pages or complex component trees
230
- - Frequently changing UI
231
- - Dynamic content (timestamps, IDs, random values)
232
-
233
- ### Prefer Inline Snapshots
234
-
235
- Keeps expected output visible in the test file:
236
-
237
- ```typescript
238
- expect(formatUser(user)).toMatchInlineSnapshot(`
239
- {
240
- "name": "Alice",
241
- "role": "admin",
242
- }
243
- `);
244
- ```
245
-
246
- ### Handle Dynamic Values
247
-
248
- ```typescript
249
- expect(result).toMatchObject({
250
- id: expect.any(String),
251
- createdAt: expect.any(Date),
252
- name: "Alice",
253
- });
254
- ```
255
-
256
- ### Snapshot Hygiene
257
-
258
- - Review snapshot diffs as carefully as code diffs in PRs
259
- - Run `vitest --update` only intentionally, never blindly
260
- - CI should fail on obsolete snapshots
261
-
262
- ## Type Testing
263
-
264
- Vitest has built-in type testing (since 2.1). Verify your types work correctly alongside runtime:
265
-
266
- ```typescript
267
- import { expectTypeOf } from "vitest";
268
-
269
- it("should return correct type", () => {
270
- const result = parseConfig(rawConfig);
271
- expectTypeOf(result).toEqualTypeOf<AppConfig>();
272
- expectTypeOf(result.port).toBeNumber();
273
- });
274
- ```
275
-
276
- ## Coverage
277
-
278
- ### Use V8 Provider
279
-
280
- Since Vitest 3.2, V8 coverage uses AST-based remapping that matches Istanbul accuracy with ~10% overhead vs Istanbul's ~300%. Always use V8:
281
-
282
- ```typescript
283
- // vite.config.ts
284
- test: {
285
- coverage: {
286
- provider: "v8",
287
- include: ["src/**/*.{ts,tsx}"],
288
- exclude: [
289
- "src/**/*.css.ts", // vanilla-extract style files
290
- "src/**/index.ts", // barrel exports
291
- "src/**/*.d.ts", // type declarations
292
- "src/locales/**", // i18n generated files
293
- ],
294
- reporter: ["text", "html", "lcov"],
295
- thresholds: {
296
- statements: 80,
297
- branches: 70,
298
- functions: 80,
299
- lines: 80,
300
- },
301
- },
302
- }
303
- ```
304
-
305
- ### Coverage Recommendations
306
-
307
- - Start with moderate thresholds (70-80%) and ratchet up as coverage grows
308
- - Focus on **branch coverage** -- it catches more real bugs than line coverage
309
- - Use per-glob overrides for critical paths (e.g., `src/utils/**` at 95%)
310
- - Use `/* v8 ignore start */` / `/* v8 ignore stop */` for intentionally uncovered code
311
-
312
- ## Vitest 4.x Features
313
-
314
- ### Test Tags
315
-
316
- Define tags with per-tag configuration. Useful for separating fast/slow tests:
317
-
318
- ```typescript
319
- // vite.config.ts
320
- test: {
321
- tags: {
322
- slow: { timeout: 30_000 },
323
- flaky: { retry: 3 },
324
- },
325
- }
326
- ```
327
-
328
- ```typescript
329
- it.tags(["slow"])("should process large dataset", async () => {
330
- // ...
331
- });
332
- ```
333
-
334
- ### Builder-Pattern Fixtures
335
-
336
- The idiomatic way to share setup in Vitest 4.x. TypeScript infers fixture types automatically:
337
-
338
- ```typescript
339
- const myTest = test
340
- .extend({ config: { timeout: 5000 } })
341
- .extend({
342
- server: async ({ config }, { onCleanup }) => {
343
- const srv = await startServer(config);
344
- onCleanup(() => srv.close());
345
- return srv;
346
- },
347
- });
348
-
349
- myTest("should connect to server", ({ server }) => {
350
- expect(server.isRunning()).toBe(true);
351
- });
352
- ```
353
-
354
- ### Detect Async Leaks
355
-
356
- Reports leaked async resources with source locations. Enable while debugging:
357
-
358
- ```typescript
359
- test: {
360
- detectAsyncLeaks: true, // Disable in CI (adds overhead)
361
- }
362
- ```
363
-
364
- ## Performance
365
-
366
- ### Pool Configuration
367
-
368
- | Pool | Best For |
369
- |---|---|
370
- | `threads` (default) | General use, SolidJS + JSDOM tests |
371
- | `forks` | Better isolation, more overhead |
372
- | `vmThreads` | VM contexts with worker parallelism |
373
-
374
- ### Sharding for CI
375
-
376
- Split tests across CI machines:
377
-
378
- ```bash
379
- # Machine 1
380
- vitest run --shard=1/3 --reporter=blob
381
-
382
- # Machine 2
383
- vitest run --shard=2/3 --reporter=blob
384
-
385
- # Machine 3
386
- vitest run --shard=3/3 --reporter=blob
387
-
388
- # Merge
389
- vitest merge-reports
390
- ```
391
-
392
- ### Other Tips
393
-
394
- - Use `test.concurrent` for independent async tests
395
- - Keep test files small and focused (Vitest parallelizes at the file level)
396
- - Avoid heavy setup in `beforeAll` that could be lazy-loaded
397
- - Use `--no-isolate` only for pure function test files, never for component tests
398
-
399
- ## Recommended Config
400
-
401
- ```typescript
402
- // vite.config.ts (test section)
403
- test: {
404
- environment: "jsdom",
405
- globals: true,
406
- setupFiles: ["./tests/setup.ts"],
407
- include: ["src/**/*.test.{ts,tsx}", "tests/**/*.test.{ts,tsx}"],
408
- restoreMocks: true,
409
- coverage: {
410
- provider: "v8",
411
- include: ["src/**/*.{ts,tsx}"],
412
- exclude: [
413
- "src/**/*.css.ts",
414
- "src/**/index.ts",
415
- "src/**/*.d.ts",
416
- "src/locales/**",
417
- ],
418
- reporter: ["text", "html", "lcov"],
419
- thresholds: {
420
- statements: 80,
421
- branches: 70,
422
- functions: 80,
423
- lines: 80,
424
- },
425
- },
426
- }
427
- ```
428
-
429
- ```typescript
430
- // tests/setup.ts
431
- import "@testing-library/jest-dom/vitest";
432
-
433
- afterEach(() => {
434
- vi.clearAllTimers();
435
- });
436
- ```
437
-
438
- ## Anti-Patterns
439
-
440
- | Anti-Pattern | Fix |
441
- |---|---|
442
- | Testing implementation details (internal state, private methods) | Test behavior and public API only |
443
- | Large snapshots (100+ lines) | Break into small focused snapshots or use explicit assertions |
444
- | Blind `--update` on snapshot failures | Review each diff individually |
445
- | `vi.mock` without cleanup | Enable `restoreMocks: true` in config |
446
- | Shared mutable state across tests | Reset state in `beforeEach` or use fixtures |
447
- | Testing framework code (that SolidJS reactivity works) | Test your logic, not the framework |
448
- | `setTimeout` in tests for async waits | Use `waitFor`, `findBy*`, or `vi.advanceTimersByTime` |
449
- | Over-mocking (mocking everything) | Mock only external boundaries (network, timers, platform APIs) |
450
- | Destructuring SolidJS props in tests | Same rule as production -- never destructure props |
451
- | Using `as any` in test code | If you need it, the API under test is wrong |
@@ -1,45 +0,0 @@
1
- # Matt Pocock's TypeScript Best Practices
2
-
3
- > A curated collection of TypeScript best practices, opinions, and patterns from [Matt Pocock](https://www.mattpocock.com/) and [Total TypeScript](https://www.totaltypescript.com/).
4
-
5
- Matt Pocock is one of the most influential TypeScript educators. These documents capture his opinionated, production-tested guidance — not generic advice, but specific stances backed by reasoning.
6
-
7
- ## Core Choices
8
-
9
- Foundational decisions that shape every TypeScript project.
10
-
11
- | Guide | Summary |
12
- |---|---|
13
- | [Type vs Interface](type-vs-interface.md) | Default to `type`. Use `interface` only for `extends`. |
14
- | [Enums & Alternatives](enums-alternatives.md) | Avoid enums. Use `as const` objects with derived unions. |
15
- | [any & unknown](any-and-unknown.md) | Ban `any` by default. Know the two legitimate exceptions. |
16
- | [TSConfig Cheat Sheet](tsconfig-cheat-sheet.md) | Recommended compiler options for every project shape. |
17
-
18
- ## Patterns
19
-
20
- Type-level patterns that make code safer and more expressive.
21
-
22
- | Guide | Summary |
23
- |---|---|
24
- | [Discriminated Unions](discriminated-unions.md) | Model states as unions — make illegal states unrepresentable. |
25
- | [Generics Patterns](generics-patterns.md) | Three patterns: types-to-types, types-to-functions, inference. |
26
- | [The `satisfies` Operator](satisfies-operator.md) | Five practical uses for type validation without losing inference. |
27
- | [Essential Patterns](essential-patterns.md) | Branded types, globals, assertion functions, classes. |
28
-
29
- ## Architecture
30
-
31
- How to organize and connect types across a codebase.
32
-
33
- | Guide | Summary |
34
- |---|---|
35
- | [Type Organization](type-organization.md) | Colocate, share at folder level, or extract to a package. |
36
- | [Deriving vs Decoupling](deriving-vs-decoupling.md) | Derive when tightly coupled. Decouple across concerns. |
37
- | [Runtime Validation](runtime-validation.md) | Use Zod at trust boundaries. Skip it for controlled inputs. |
38
-
39
- ## Micro-Opinions
40
-
41
- Small but specific stances on everyday TypeScript choices.
42
-
43
- | Guide | Summary |
44
- |---|---|
45
- | [Micro-Opinions](micro-opinions.md) | Return types, `const` vs `let`, `T[]` vs `Array<T>`, method shorthand, `Function` type. |
@@ -1,73 +0,0 @@
1
- # `any` & `unknown`
2
-
3
- > Source: [any Considered Harmful, Except For These Cases](https://www.totaltypescript.com/any-considered-harmful) — Matt Pocock
4
-
5
- ## The Rule
6
-
7
- **Ban `any` from your codebase.** Enable the ESLint rule that prevents its use and avoid it wherever possible.
8
-
9
- `any` disables TypeScript's core value proposition: type checking, autocomplete, and safety guarantees. It's a contagion — once `any` enters an expression, it spreads through assignments and return values.
10
-
11
- ## What to Use Instead
12
-
13
- | Situation | Use |
14
- |---|---|
15
- | Type is genuinely unknown | `unknown` (then narrow before use) |
16
- | Type varies but has a consistent shape | Generics with constraints |
17
- | You know the data shape | The specific type |
18
-
19
- ```typescript
20
- // Instead of any
21
- function parse(input: unknown): User {
22
- if (typeof input === "object" && input !== null && "name" in input) {
23
- return input as User;
24
- }
25
- throw new Error("Invalid input");
26
- }
27
- ```
28
-
29
- ## The Two Legitimate Exceptions
30
-
31
- Matt identifies two advanced scenarios where `any` is the right choice.
32
-
33
- ### Exception 1: Type Argument Constraints in Generics
34
-
35
- When building generic utility types, `unknown` in constraints can be too restrictive:
36
-
37
- ```typescript
38
- // This is too restrictive — only works with specific function shapes
39
- type ReturnType<T extends (...args: unknown[]) => unknown> = ...
40
-
41
- // any[] constraint is correct — "we don't care what the function accepts"
42
- type ReturnType<T extends (...args: any[]) => any> = ...
43
- ```
44
-
45
- Using `any[]` here is intentional: you're declaring that the constraint should accept **any function**, not that you're bypassing safety.
46
-
47
- ### Exception 2: Conditional Types in Generic Functions
48
-
49
- TypeScript's type narrowing sometimes can't verify that conditional type logic matches runtime behavior:
50
-
51
- ```typescript
52
- function processValue<T extends string | number>(
53
- value: T
54
- ): T extends string ? string[] : number {
55
- if (typeof value === "string") {
56
- return value.split("") as any; // TypeScript can't narrow conditional return types
57
- }
58
- return (value as number) * 2 as any;
59
- }
60
- ```
61
-
62
- In these cases, Matt recommends using `as any` **and adding a unit test** to validate the function's behavior at runtime.
63
-
64
- ## The Pragmatic Approach
65
-
66
- When you genuinely need `any`, use an `eslint-disable` comment to make it explicit and reviewable:
67
-
68
- ```typescript
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- type Callback = (...args: any[]) => void;
71
- ```
72
-
73
- This creates a searchable record of every `any` in your codebase, making them easy to audit and justify in code review.
@@ -1,83 +0,0 @@
1
- # Deriving vs Decoupling
2
-
3
- > Source: [Deriving vs Decoupling: When NOT To Be A TypeScript Wizard](https://www.totaltypescript.com/deriving-vs-decoupling) — Matt Pocock
4
-
5
- ## The Distinction
6
-
7
- - **Deriving** = creating a type that depends on another type's structure (`Pick`, `Omit`, `typeof`, mapped types)
8
- - **Decoupling** = creating independent types with no dependency between them
9
-
10
- The temptation to derive comes from TypeScript's powerful type manipulation. But cleverness isn't always correctness.
11
-
12
- ## When to Decouple
13
-
14
- Decouple when types serve **different concerns** — different responsibilities with different reasons to change.
15
-
16
- ```typescript
17
- // Database layer
18
- type User = {
19
- id: string;
20
- name: string;
21
- email: string;
22
- imageUrl: string;
23
- passwordHash: string;
24
- };
25
-
26
- // UI layer — DON'T derive from User
27
- type AvatarProps = {
28
- imageUrl: string;
29
- name: string;
30
- };
31
- ```
32
-
33
- Why not `Pick<User, "imageUrl" | "name">`? Because:
34
- - `User` lives in database code; `AvatarProps` is UI logic
35
- - They have different reasons to change
36
- - If `User.imageUrl` is renamed to `User.avatarUrl`, should every UI component break? Probably not.
37
- - The component should remain portable even if the data model moves
38
-
39
- As Matt puts it: "It's smarter to do the simple thing."
40
-
41
- ## When to Derive
42
-
43
- Derive when types share a **common concern** and are tightly coupled by design.
44
-
45
- ### From `as const` objects
46
-
47
- ```typescript
48
- const albumTypes = {
49
- CD: "cd",
50
- VINYL: "vinyl",
51
- DIGITAL: "digital",
52
- } as const;
53
-
54
- type AlbumType = (typeof albumTypes)[keyof typeof albumTypes];
55
- // "cd" | "vinyl" | "digital"
56
- ```
57
-
58
- Maintaining a separate union and object would mean updating two places for every change — pure busywork.
59
-
60
- ### Related variants
61
-
62
- ```typescript
63
- type User = {
64
- id: string;
65
- name: string;
66
- email: string;
67
- };
68
-
69
- type UserWithoutId = Omit<User, "id">;
70
- ```
71
-
72
- These are directly related — a `UserWithoutId` is meaningless outside the context of `User`.
73
-
74
- ## Decision Framework
75
-
76
- > "The decision to derive or decouple is all about reducing your future workload."
77
-
78
- | Signal | Action |
79
- |---|---|
80
- | Changing one type should always change the other | **Derive** |
81
- | Types serve different layers or concerns | **Decouple** |
82
- | Derivation adds complexity for marginal DRY benefit | **Decouple** |
83
- | Types share the same source of truth | **Derive** |