@girardmedia/bootspring 3.3.2 → 3.4.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/assets/agents/accessibility-auditor.md +39 -0
- package/assets/agents/api-designer.md +40 -0
- package/assets/agents/auth-implementer.md +64 -0
- package/assets/agents/bug-hunter.md +42 -0
- package/assets/agents/bundle-analyzer.md +40 -0
- package/assets/agents/cache-optimizer.md +55 -0
- package/assets/agents/changelog-writer.md +55 -0
- package/assets/agents/ci-cd-builder.md +40 -0
- package/assets/agents/code-explainer.md +39 -0
- package/assets/agents/code-reviewer.md +39 -0
- package/assets/agents/cost-optimizer.md +57 -0
- package/assets/agents/cron-scheduler.md +51 -0
- package/assets/agents/data-seeder.md +56 -0
- package/assets/agents/database-architect.md +40 -0
- package/assets/agents/dependency-updater.md +40 -0
- package/assets/agents/deploy-checker.md +40 -0
- package/assets/agents/docker-optimizer.md +40 -0
- package/assets/agents/documentation-writer.md +40 -0
- package/assets/agents/email-builder.md +55 -0
- package/assets/agents/env-setup.md +40 -0
- package/assets/agents/error-handler.md +40 -0
- package/assets/agents/eslint-fixer.md +46 -0
- package/assets/agents/feature-flagger.md +69 -0
- package/assets/agents/git-detective.md +39 -0
- package/assets/agents/graphql-builder.md +60 -0
- package/assets/agents/incident-responder.md +59 -0
- package/assets/agents/log-analyzer.md +39 -0
- package/assets/agents/migration-planner.md +41 -0
- package/assets/agents/monorepo-navigator.md +39 -0
- package/assets/agents/nextjs-expert.md +57 -0
- package/assets/agents/notification-builder.md +56 -0
- package/assets/agents/onboarding-guide.md +39 -0
- package/assets/agents/performance-profiler.md +40 -0
- package/assets/agents/prisma-expert.md +57 -0
- package/assets/agents/rate-limiter.md +58 -0
- package/assets/agents/react-expert.md +58 -0
- package/assets/agents/refactorer.md +42 -0
- package/assets/agents/regex-builder.md +46 -0
- package/assets/agents/release-manager.md +40 -0
- package/assets/agents/s3-manager.md +58 -0
- package/assets/agents/schema-validator.md +40 -0
- package/assets/agents/search-builder.md +62 -0
- package/assets/agents/security-auditor.md +39 -0
- package/assets/agents/sitemap-generator.md +53 -0
- package/assets/agents/stripe-integrator.md +59 -0
- package/assets/agents/tailwind-expert.md +55 -0
- package/assets/agents/tech-debt-tracker.md +39 -0
- package/assets/agents/test-writer.md +42 -0
- package/assets/agents/type-fixer.md +45 -0
- package/assets/agents/webhook-builder.md +54 -0
- package/assets/rules/cpp.md +53 -0
- package/assets/rules/css.md +52 -0
- package/assets/rules/go.md +50 -0
- package/assets/rules/html.md +52 -0
- package/assets/rules/java.md +51 -0
- package/assets/rules/kotlin.md +50 -0
- package/assets/rules/php.md +51 -0
- package/assets/rules/python.md +51 -0
- package/assets/rules/ruby.md +51 -0
- package/assets/rules/rust.md +49 -0
- package/assets/rules/shell.md +52 -0
- package/assets/rules/sql.md +49 -0
- package/assets/rules/swift.md +50 -0
- package/assets/rules/typescript.md +52 -0
- package/assets/rules/yaml-json.md +51 -0
- package/assets/skills/accessibility.md +210 -0
- package/assets/skills/agent-patterns.md +387 -0
- package/assets/skills/ai-integration.md +263 -0
- package/assets/skills/animation-patterns.md +224 -0
- package/assets/skills/api-design.md +218 -0
- package/assets/skills/api-gateway.md +341 -0
- package/assets/skills/api-versioning.md +226 -0
- package/assets/skills/astro-patterns.md +233 -0
- package/assets/skills/auth-patterns.md +248 -0
- package/assets/skills/aws-patterns.md +171 -0
- package/assets/skills/background-jobs.md +162 -0
- package/assets/skills/browser-extensions.md +309 -0
- package/assets/skills/caching-patterns.md +253 -0
- package/assets/skills/ci-cd.md +251 -0
- package/assets/skills/cli-development.md +296 -0
- package/assets/skills/code-review.md +185 -0
- package/assets/skills/cron-patterns.md +327 -0
- package/assets/skills/data-fetching.md +231 -0
- package/assets/skills/database-migrations.md +346 -0
- package/assets/skills/database-patterns.md +219 -0
- package/assets/skills/debugging.md +281 -0
- package/assets/skills/design-system.md +289 -0
- package/assets/skills/django-patterns.md +182 -0
- package/assets/skills/docker-patterns.md +235 -0
- package/assets/skills/e2e-testing.md +287 -0
- package/assets/skills/edge-computing.md +268 -0
- package/assets/skills/electron-patterns.md +266 -0
- package/assets/skills/email-templates.md +206 -0
- package/assets/skills/error-handling.md +265 -0
- package/assets/skills/event-driven.md +232 -0
- package/assets/skills/express-patterns.md +239 -0
- package/assets/skills/fastapi-patterns.md +198 -0
- package/assets/skills/feature-flags.md +212 -0
- package/assets/skills/figma-to-code.md +298 -0
- package/assets/skills/file-upload.md +228 -0
- package/assets/skills/forms-patterns.md +264 -0
- package/assets/skills/gcp-patterns.md +189 -0
- package/assets/skills/git-workflow.md +187 -0
- package/assets/skills/golang-patterns.md +185 -0
- package/assets/skills/graphql-patterns.md +244 -0
- package/assets/skills/i18n-patterns.md +172 -0
- package/assets/skills/image-processing.md +350 -0
- package/assets/skills/java-springboot.md +226 -0
- package/assets/skills/kotlin-patterns.md +207 -0
- package/assets/skills/kubernetes-patterns.md +326 -0
- package/assets/skills/laravel-patterns.md +261 -0
- package/assets/skills/llm-fine-tuning.md +335 -0
- package/assets/skills/load-testing.md +303 -0
- package/assets/skills/logging-observability.md +228 -0
- package/assets/skills/markdown-processing.md +318 -0
- package/assets/skills/mcp-server-patterns.md +292 -0
- package/assets/skills/microservices.md +272 -0
- package/assets/skills/migration-patterns.md +239 -0
- package/assets/skills/mongodb-patterns.md +189 -0
- package/assets/skills/monorepo-patterns.md +287 -0
- package/assets/skills/nextjs-app-router.md +237 -0
- package/assets/skills/notification-patterns.md +348 -0
- package/assets/skills/oauth-patterns.md +246 -0
- package/assets/skills/payment-integration.md +222 -0
- package/assets/skills/pdf-generation.md +307 -0
- package/assets/skills/performance-optimization.md +277 -0
- package/assets/skills/php-patterns.md +210 -0
- package/assets/skills/prisma-patterns.md +241 -0
- package/assets/skills/prompt-engineering.md +193 -0
- package/assets/skills/pwa-patterns.md +247 -0
- package/assets/skills/python-patterns.md +158 -0
- package/assets/skills/python-testing.md +172 -0
- package/assets/skills/queue-patterns.md +295 -0
- package/assets/skills/rag-patterns.md +159 -0
- package/assets/skills/rate-limiting.md +319 -0
- package/assets/skills/react-components.md +201 -0
- package/assets/skills/react-native-patterns.md +299 -0
- package/assets/skills/real-time-patterns.md +181 -0
- package/assets/skills/redis-patterns.md +188 -0
- package/assets/skills/refactoring.md +218 -0
- package/assets/skills/regex-patterns.md +191 -0
- package/assets/skills/remix-patterns.md +262 -0
- package/assets/skills/responsive-design.md +199 -0
- package/assets/skills/ruby-rails-patterns.md +178 -0
- package/assets/skills/rust-patterns.md +211 -0
- package/assets/skills/search-patterns.md +227 -0
- package/assets/skills/security-hardening.md +237 -0
- package/assets/skills/seo-patterns.md +179 -0
- package/assets/skills/serverless-patterns.md +223 -0
- package/assets/skills/sql-optimization.md +154 -0
- package/assets/skills/state-management.md +254 -0
- package/assets/skills/storybook-patterns.md +330 -0
- package/assets/skills/svelte-patterns.md +258 -0
- package/assets/skills/swift-patterns.md +227 -0
- package/assets/skills/tailwind-patterns.md +272 -0
- package/assets/skills/tdd-workflow.md +199 -0
- package/assets/skills/terraform-patterns.md +270 -0
- package/assets/skills/testing-react.md +240 -0
- package/assets/skills/testing-vitest.md +232 -0
- package/assets/skills/typescript-strict.md +159 -0
- package/assets/skills/video-processing.md +340 -0
- package/assets/skills/vue-patterns.md +247 -0
- package/assets/skills/web-workers.md +327 -0
- package/assets/skills/webhooks-patterns.md +283 -0
- package/assets/skills/websocket-patterns.md +306 -0
- package/dist/cli/index.js +941 -958
- package/dist/core/index.d.ts +341 -11
- package/dist/core.js +58 -95
- package/dist/mcp/index.d.ts +33 -1
- package/dist/mcp-server.js +177 -255
- package/package.json +4 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: testing-vitest
|
|
3
|
+
description: Vitest testing patterns including describe/it/expect, mocking with vi.*, snapshot testing, and coverage configuration.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Vitest Testing Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
Use Vitest when writing unit and integration tests for TypeScript or JavaScript projects. Vitest is the preferred test runner for Vite-based projects and any modern TS codebase that benefits from native ESM support and fast HMR-driven test execution. Apply these patterns whenever you need to test pure functions, modules with dependencies, async code, or React components.
|
|
10
|
+
|
|
11
|
+
## How It Works
|
|
12
|
+
|
|
13
|
+
### Basic Test Structure
|
|
14
|
+
|
|
15
|
+
Every test file follows the describe/it/expect pattern:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
19
|
+
import { calculateDiscount } from '../src/pricing';
|
|
20
|
+
|
|
21
|
+
describe('calculateDiscount', () => {
|
|
22
|
+
let basePrice: number;
|
|
23
|
+
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
basePrice = 100;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('applies percentage discount correctly', () => {
|
|
29
|
+
const result = calculateDiscount(basePrice, { type: 'percent', value: 20 });
|
|
30
|
+
expect(result).toBe(80);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('applies fixed discount correctly', () => {
|
|
34
|
+
const result = calculateDiscount(basePrice, { type: 'fixed', value: 15 });
|
|
35
|
+
expect(result).toBe(85);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('never returns a negative price', () => {
|
|
39
|
+
const result = calculateDiscount(basePrice, { type: 'fixed', value: 200 });
|
|
40
|
+
expect(result).toBe(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('throws on invalid discount type', () => {
|
|
44
|
+
expect(() => calculateDiscount(basePrice, { type: 'bogus' as any, value: 10 }))
|
|
45
|
+
.toThrow('Unknown discount type: bogus');
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Mocking with vi.*
|
|
51
|
+
|
|
52
|
+
Replace dependencies with controlled implementations:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
56
|
+
import { fetchUserProfile } from '../src/user-service';
|
|
57
|
+
|
|
58
|
+
// Mock an entire module
|
|
59
|
+
vi.mock('../src/api-client', () => ({
|
|
60
|
+
apiClient: {
|
|
61
|
+
get: vi.fn(),
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
import { apiClient } from '../src/api-client';
|
|
66
|
+
|
|
67
|
+
describe('fetchUserProfile', () => {
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
vi.clearAllMocks();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns parsed user data', async () => {
|
|
73
|
+
vi.mocked(apiClient.get).mockResolvedValueOnce({
|
|
74
|
+
data: { id: '1', name: 'Alice', email: 'alice@example.com' },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const profile = await fetchUserProfile('1');
|
|
78
|
+
|
|
79
|
+
expect(apiClient.get).toHaveBeenCalledWith('/users/1');
|
|
80
|
+
expect(profile).toEqual({ id: '1', name: 'Alice', email: 'alice@example.com' });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('throws on network error', async () => {
|
|
84
|
+
vi.mocked(apiClient.get).mockRejectedValueOnce(new Error('Network timeout'));
|
|
85
|
+
|
|
86
|
+
await expect(fetchUserProfile('1')).rejects.toThrow('Network timeout');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Spying on Methods
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
95
|
+
|
|
96
|
+
describe('Logger', () => {
|
|
97
|
+
it('tracks console.warn calls', () => {
|
|
98
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
99
|
+
|
|
100
|
+
console.warn('deprecation notice');
|
|
101
|
+
|
|
102
|
+
expect(warnSpy).toHaveBeenCalledOnce();
|
|
103
|
+
expect(warnSpy).toHaveBeenCalledWith('deprecation notice');
|
|
104
|
+
|
|
105
|
+
warnSpy.mockRestore();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Timer Mocking
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
114
|
+
import { debounce } from '../src/utils';
|
|
115
|
+
|
|
116
|
+
describe('debounce', () => {
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
vi.useFakeTimers();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
afterEach(() => {
|
|
122
|
+
vi.useRealTimers();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('delays execution by the specified ms', () => {
|
|
126
|
+
const fn = vi.fn();
|
|
127
|
+
const debounced = debounce(fn, 300);
|
|
128
|
+
|
|
129
|
+
debounced();
|
|
130
|
+
expect(fn).not.toHaveBeenCalled();
|
|
131
|
+
|
|
132
|
+
vi.advanceTimersByTime(299);
|
|
133
|
+
expect(fn).not.toHaveBeenCalled();
|
|
134
|
+
|
|
135
|
+
vi.advanceTimersByTime(1);
|
|
136
|
+
expect(fn).toHaveBeenCalledOnce();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Snapshot Testing
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
import { describe, it, expect } from 'vitest';
|
|
145
|
+
import { generateConfig } from '../src/config-builder';
|
|
146
|
+
|
|
147
|
+
describe('generateConfig', () => {
|
|
148
|
+
it('matches snapshot for production env', () => {
|
|
149
|
+
const config = generateConfig({ env: 'production', region: 'us-east-1' });
|
|
150
|
+
expect(config).toMatchInlineSnapshot(`
|
|
151
|
+
{
|
|
152
|
+
"cache": true,
|
|
153
|
+
"debug": false,
|
|
154
|
+
"logLevel": "error",
|
|
155
|
+
"region": "us-east-1",
|
|
156
|
+
}
|
|
157
|
+
`);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('matches file snapshot for full config', () => {
|
|
161
|
+
const config = generateConfig({ env: 'staging', region: 'eu-west-1' });
|
|
162
|
+
expect(config).toMatchSnapshot();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Coverage Configuration
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// vitest.config.ts
|
|
171
|
+
import { defineConfig } from 'vitest/config';
|
|
172
|
+
|
|
173
|
+
export default defineConfig({
|
|
174
|
+
test: {
|
|
175
|
+
globals: true,
|
|
176
|
+
environment: 'node',
|
|
177
|
+
include: ['**/*.test.ts', '**/*.spec.ts'],
|
|
178
|
+
coverage: {
|
|
179
|
+
provider: 'v8',
|
|
180
|
+
reporter: ['text', 'html', 'lcov'],
|
|
181
|
+
include: ['src/**/*.ts'],
|
|
182
|
+
exclude: ['src/**/*.d.ts', 'src/**/__tests__/**', 'src/**/index.ts'],
|
|
183
|
+
thresholds: {
|
|
184
|
+
branches: 80,
|
|
185
|
+
functions: 80,
|
|
186
|
+
lines: 80,
|
|
187
|
+
statements: 80,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Parameterized Tests
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { describe, it, expect } from 'vitest';
|
|
198
|
+
import { slugify } from '../src/string-utils';
|
|
199
|
+
|
|
200
|
+
describe('slugify', () => {
|
|
201
|
+
it.each([
|
|
202
|
+
['Hello World', 'hello-world'],
|
|
203
|
+
[' Leading spaces ', 'leading-spaces'],
|
|
204
|
+
['Special Ch@r$!', 'special-chr'],
|
|
205
|
+
['already-slugged', 'already-slugged'],
|
|
206
|
+
['UPPERCASE', 'uppercase'],
|
|
207
|
+
])('slugify(%j) => %j', (input, expected) => {
|
|
208
|
+
expect(slugify(input)).toBe(expected);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Examples
|
|
214
|
+
|
|
215
|
+
| Pattern | Use Case |
|
|
216
|
+
|---------|----------|
|
|
217
|
+
| `vi.fn()` | Create a standalone mock function for callbacks |
|
|
218
|
+
| `vi.mock('module')` | Replace an entire module import |
|
|
219
|
+
| `vi.spyOn(obj, 'method')` | Watch calls without replacing implementation |
|
|
220
|
+
| `vi.useFakeTimers()` | Control setTimeout/setInterval/Date |
|
|
221
|
+
| `vi.mocked(fn)` | Get typed mock from an auto-mocked function |
|
|
222
|
+
| `toMatchInlineSnapshot()` | Pin output directly in the test file |
|
|
223
|
+
| `it.each([...])` | Run the same assertion with different inputs |
|
|
224
|
+
|
|
225
|
+
## Checklist
|
|
226
|
+
- [ ] Every test file imports from `vitest`, not from `jest` or `@jest/globals`
|
|
227
|
+
- [ ] `vi.clearAllMocks()` is called in `beforeEach` when mocks are shared across tests
|
|
228
|
+
- [ ] Fake timers are restored in `afterEach` with `vi.useRealTimers()`
|
|
229
|
+
- [ ] Spies are restored with `mockRestore()` to prevent leakage between tests
|
|
230
|
+
- [ ] Coverage thresholds are set in `vitest.config.ts` and enforced in CI
|
|
231
|
+
- [ ] Async tests use `await expect(...).resolves` or `.rejects` instead of try/catch
|
|
232
|
+
- [ ] Snapshot files are committed and reviewed in PRs for unexpected changes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: typescript-strict
|
|
3
|
+
description: Enforce strict TypeScript patterns to catch bugs at compile time instead of runtime.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Strict TypeScript Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Apply these patterns whenever you write or review TypeScript code. Strict typing
|
|
11
|
+
eliminates entire categories of runtime errors — null reference crashes, missing
|
|
12
|
+
case handling, accidental type coercion, and silent data corruption. Use this
|
|
13
|
+
skill from day one of a project, not as a retrofit.
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
### 1. Ban `any` — Use `unknown` Instead
|
|
18
|
+
|
|
19
|
+
`any` disables the type checker. `unknown` forces you to narrow before use.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Bad — silent runtime crash
|
|
23
|
+
function parse(input: any) {
|
|
24
|
+
return input.data.items; // no error, but blows up if input is null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Good — compiler forces narrowing
|
|
28
|
+
function parse(input: unknown): Item[] {
|
|
29
|
+
if (typeof input === 'object' && input !== null && 'data' in input) {
|
|
30
|
+
const data = (input as { data: { items: Item[] } }).data;
|
|
31
|
+
return data.items;
|
|
32
|
+
}
|
|
33
|
+
throw new ParseError('Invalid input shape');
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Enable `"noImplicitAny": true` in tsconfig. Lint with `@typescript-eslint/no-explicit-any`.
|
|
38
|
+
|
|
39
|
+
### 2. Discriminated Unions for State Modeling
|
|
40
|
+
|
|
41
|
+
Model mutually exclusive states with a literal discriminant field.
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
type AsyncState<T> =
|
|
45
|
+
| { status: 'idle' }
|
|
46
|
+
| { status: 'loading' }
|
|
47
|
+
| { status: 'success'; data: T }
|
|
48
|
+
| { status: 'error'; error: Error };
|
|
49
|
+
|
|
50
|
+
function render(state: AsyncState<User>) {
|
|
51
|
+
switch (state.status) {
|
|
52
|
+
case 'idle': return <Placeholder />;
|
|
53
|
+
case 'loading': return <Spinner />;
|
|
54
|
+
case 'success': return <Profile user={state.data} />; // data is narrowed
|
|
55
|
+
case 'error': return <Alert message={state.error.message} />;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 3. Exhaustive Switch Checks
|
|
61
|
+
|
|
62
|
+
Use `never` to catch unhandled union members at compile time.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function assertNever(x: never): never {
|
|
66
|
+
throw new Error(`Unexpected value: ${JSON.stringify(x)}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleEvent(event: AppEvent) {
|
|
70
|
+
switch (event.type) {
|
|
71
|
+
case 'click': return onClick(event);
|
|
72
|
+
case 'keydown': return onKeydown(event);
|
|
73
|
+
default: return assertNever(event); // compile error if a case is missing
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 4. Branded Types for Domain Safety
|
|
79
|
+
|
|
80
|
+
Prevent mixing structurally identical but semantically different values.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
type UserId = string & { readonly __brand: 'UserId' };
|
|
84
|
+
type OrderId = string & { readonly __brand: 'OrderId' };
|
|
85
|
+
|
|
86
|
+
function createUserId(raw: string): UserId { return raw as UserId; }
|
|
87
|
+
function createOrderId(raw: string): OrderId { return raw as OrderId; }
|
|
88
|
+
|
|
89
|
+
function getUser(id: UserId): Promise<User> { /* ... */ }
|
|
90
|
+
|
|
91
|
+
const orderId = createOrderId('abc-123');
|
|
92
|
+
getUser(orderId); // Compile error — OrderId is not assignable to UserId
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 5. Const Assertions and Readonly
|
|
96
|
+
|
|
97
|
+
Lock down object shapes and array contents.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const ROLES = ['admin', 'editor', 'viewer'] as const;
|
|
101
|
+
type Role = (typeof ROLES)[number]; // 'admin' | 'editor' | 'viewer'
|
|
102
|
+
|
|
103
|
+
interface Config {
|
|
104
|
+
readonly apiUrl: string;
|
|
105
|
+
readonly retries: number;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 6. Template Literal Types for String Patterns
|
|
110
|
+
|
|
111
|
+
Constrain strings to known patterns without regex at runtime.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
115
|
+
type Route = `/${string}`;
|
|
116
|
+
type Endpoint = `${HttpMethod} ${Route}`;
|
|
117
|
+
|
|
118
|
+
const endpoint: Endpoint = 'GET /users'; // valid
|
|
119
|
+
const bad: Endpoint = 'FETCH /users'; // compile error
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 7. Strict tsconfig Settings
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"compilerOptions": {
|
|
127
|
+
"strict": true,
|
|
128
|
+
"noUncheckedIndexedAccess": true,
|
|
129
|
+
"noImplicitReturns": true,
|
|
130
|
+
"noFallthroughCasesInSwitch": true,
|
|
131
|
+
"exactOptionalPropertyTypes": true,
|
|
132
|
+
"noPropertyAccessFromIndexSignature": true
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`noUncheckedIndexedAccess` is the most impactful — it makes `array[0]` return
|
|
138
|
+
`T | undefined` instead of `T`, catching out-of-bounds access.
|
|
139
|
+
|
|
140
|
+
## Examples
|
|
141
|
+
|
|
142
|
+
| Pattern | Catches | Cost |
|
|
143
|
+
|---------|---------|------|
|
|
144
|
+
| Ban `any` | Type coercion bugs | Slightly more verbose parsers |
|
|
145
|
+
| Discriminated unions | Impossible state combinations | ~2 extra lines per type |
|
|
146
|
+
| Exhaustive checks | Unhandled cases after adding variants | One utility function |
|
|
147
|
+
| Branded types | ID mixups across domain boundaries | Factory function per type |
|
|
148
|
+
| `noUncheckedIndexedAccess` | Array/object out-of-bounds | More `?.` and null checks |
|
|
149
|
+
|
|
150
|
+
## Checklist
|
|
151
|
+
|
|
152
|
+
- [ ] `"strict": true` is enabled in tsconfig with no per-file overrides
|
|
153
|
+
- [ ] `noUncheckedIndexedAccess` is enabled
|
|
154
|
+
- [ ] Zero `any` in application code (vendored types excluded)
|
|
155
|
+
- [ ] All union switches use exhaustive `assertNever` in the default branch
|
|
156
|
+
- [ ] Domain IDs use branded types, not raw strings
|
|
157
|
+
- [ ] Object configs use `as const` or `Readonly<>` where mutation is unintended
|
|
158
|
+
- [ ] ESLint rule `@typescript-eslint/no-explicit-any` is set to error
|
|
159
|
+
- [ ] CI fails on type errors — `tsc --noEmit` is in the pipeline
|