typescript について
JARVIS の開発者向け TypeScript スキルです。厳格な型付け、ランタイム検証、テスト、性能、セキュリティの実践方法を扱います。
主な機能
- 01厳格なコンパイラー設定と型安全性
- 02Zod による外部データ検証
- 03ブランド型と型ガード
- 04TDD と検証のワークフロー
- 05性能とセキュリティのパターン
活用シーン
- 01アプリケーションデータの型付きインターフェース定義
- 02外部入力のランタイム検証
- 03ステートマシンと ID のモデル化
- 04TypeScript の変更のテストとレビュー
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ martinholovsky/ claude-skills-generator --skill typescriptインストール後、Claude Code で/typescriptを入力して読み込まれたか確認します。
同梱ファイルreferences/advanced-patterns.mdreferences/security-examples.md
詳細
TypeScript 開発スキル
ファイル構成:このスキルでは分割構成を使用しています。高度なパターンやセキュリティの例については、
references/を参照してください。
1. 概要
このスキルでは、JARVIS AI アシスタント向けの TypeScript の専門知識を提供し、Vue コンポーネント、API ルート、3D レンダリング、状態管理など、コードベース全体で型安全性を確保します。
リスクレベル:中 — 型システムによって実行時エラーを防止し、契約を適用できますが、設定を誤るとセキュリティ上の欠陥につながる可能性があります
主なユースケース:
- JARVIS システムのデータに対する型安全なインターフェースの定義
- Zod スキーマを使用した実行時検証
- 再利用可能な HUD コンポーネント向けのジェネリックパターン
- クラッシュを防ぐ厳密な null チェック
2. 中核となる責務
2.1 基本原則
- まず TDD:実装の前にテストを記述します — レッド、グリーン、リファクタリング
- パフォーマンスを意識する:メモ化、遅延読み込み、効率的なパターンを適用します
- 常に厳格モード:すべての厳格なコンパイラーオプションを有効にします — 近道はしません
- 境界では型を明示する:モジュール境界では、常に関数のパラメーターと戻り値に型を指定します
- 実行時検証:TypeScript の型は実行時には消えるため、外部データには Zod を使用します
anyで逃げない:anyではなくunknownを使用し、型ガードで型を絞り込みます- デフォルトで不変にする:データの整合性を保つため、
readonlyとas constを優先します - 判別可能なユニオン:状態機械やエラー処理にはタグ付きユニオンを使用します
- ブランド型:ID や機密性の高い値には名目的な型を作成します
3. 技術スタックとバージョン
3.1 推奨バージョン
| パッケージ | バージョン | セキュリティに関する注意事項 |
|---|---|---|
| typescript | ^5.3.0 | 型推論が改善された最新の安定版 |
| zod | ^3.22.0 | 実行時検証、スキーマファースト |
| @types/node | ^20.0.0 | Node.js のバージョンに合わせます |
3.2 コンパイラー設定
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"forceConsistentCasingInFileNames": true,
"verbatimModuleSyntax": true,
"moduleResolution": "bundler",
"target": "ES2022",
"lib": ["ES2022", "DOM", "DOM.Iterable"]
}
}
4. 実装パターン
4.1 セキュリティのためのブランド型
// types/branded.ts
declare const __brand: unique symbol
type Brand<T, B> = T & { [__brand]: B }
// ✅ Prevent accidental mixing of IDs
export type UserId = Brand<string, 'UserId'>
export type SessionId = Brand<string, 'SessionId'>
export type CommandId = Brand<string, 'CommandId'>
// Factory functions with validation
export function createUserId(id: string): UserId {
if (!/^usr_[a-zA-Z0-9]{16}$/.test(id)) {
throw new Error('Invalid user ID format')
}
return id as UserId
}
// ✅ Type system prevents mixing IDs
function getUser(id: UserId): User { /* ... */ }
function getSession(id: SessionId): Session { /* ... */ }
// This won't compile:
// getUser(sessionId) // Error: SessionId not assignable to UserId
4.2 状態管理のための判別可能なユニオン
// types/jarvis-state.ts
// ✅ Type-safe state machine
type JARVISState =
| { status: 'idle' }
| { status: 'listening'; startTime: number }
| { status: 'processing'; commandId: CommandId }
| { status: 'responding'; response: string; confidence: number }
| { status: 'error'; error: JARVISError; retryCount: number }
// ✅ Exhaustive handling
function handleState(state: JARVISState): string {
switch (state.status) {
case 'idle':
return 'Ready'
case 'listening':
return `Listening for ${Date.now() - state.startTime}ms`
case 'processing':
return `Processing ${state.commandId}`
case 'responding':
return `${state.response} (${state.confidence}%)`
case 'error':
return `Error: ${state.error.message}`
default:
// ✅ Compile-time exhaustiveness check
const _exhaustive: never = state
return _exhaustive
}
}
4.3 Zod による実行時検証
// schemas/command.ts
import { z } from 'zod'
// ✅ Schema-first approach
export const commandSchema = z.object({
id: z.string().regex(/^cmd_[a-zA-Z0-9]{16}$/),
action: z.enum(['navigate', 'control', 'query', 'configure']),
target: z.string().min(1).max(100),
parameters: z.record(z.unknown()).optional(),
timestamp: z.number().int().positive(),
priority: z.number().min(0).max(10).default(5)
})
// ✅ Infer TypeScript type from schema
export type Command = z.infer<typeof commandSchema>
// ✅ Parse with full validation
export function parseCommand(data: unknown): Command {
return commandSchema.parse(data)
}
// ✅ Safe parse for error handling
export function tryParseCommand(data: unknown): Command | null {
const result = commandSchema.safeParse(data)
return result.success ? result.data : null
}
4.4 HUD コンポーネント向けのジェネリックパターン
// ✅ Generic with constraints - ensures type safety for metrics
interface MetricConfig<T extends Record<string, number>> {
metrics: T
thresholds: { [K in keyof T]: { warning: number; critical: number } }
}
type SystemMetrics = { cpu: number; memory: number }
const config: MetricConfig<SystemMetrics> = {
metrics: { cpu: 45, memory: 72 },
thresholds: {
cpu: { warning: 70, critical: 90 },
memory: { warning: 80, critical: 95 }
}
}
4.5 型を絞り込むための型ガード
// ✅ Type predicate for safe narrowing
function isSuccessResponse<T>(
response: APIResponse<T>
): response is APIResponse<T> & { success: true; data: T } {
return response.success && response.data !== undefined
}
// Usage - type automatically narrowed
if (isSuccessResponse(response)) {
return response.data // ✅ Type is T, not T | undefined
}
4.6 JARVIS 向けのユーティリティ型
// types/utilities.ts
// ✅ Deep readonly for immutable state
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K]
}
// ✅ Make specific keys required
type RequireKeys<T, K extends keyof T> = T & Required<Pick<T, K>>
// ✅ Extract event payloads
type EventPayload<T> = T extends { payload: infer P } ? P : never
// ✅ Async function return type
type AsyncReturnType<T extends (...args: any[]) => Promise<any>> =
T extends (...args: any[]) => Promise<infer R> ? R : never
5. 実装ワークフロー(TDD)
ステップ 1:まず失敗するテストを書く
// tests/utils/command-parser.test.ts
import { describe, it, expect } from 'vitest'
import { parseCommand } from '@/utils/command-parser'
describe('parseCommand', () => {
it('should parse valid command', () => {
expect(parseCommand('open settings')).toEqual({
action: 'open', target: 'settings', parameters: {}
})
})
it('should extract parameters', () => {
expect(parseCommand('set volume to 80')).toEqual({
action: 'set', target: 'volume', parameters: { value: 80 }
})
})
it('should throw on empty command', () => {
expect(() => parseCommand('')).toThrow('Command cannot be empty')
})
})
ステップ 2:テストを通すための最小限の実装を行う
テストをグリーンにするために必要なコードだけを記述します。
ステップ 3:必要に応じてリファクタリングする
テストをグリーンに保ちながら、コード品質を向上させます。
ステップ 4:完全な検証を実行する
npx vitest run # Unit tests
npx eslint . --ext .ts,.tsx # Linting
npx tsc --noEmit # Type checking
6. パフォーマンスパターン
6.1 メモ化
// ❌ BAD - Recalculates on every render
const processed = data.map(item => heavyTransform(item))
// ✅ GOOD - Memoized computation
import { computed } from 'vue'
const processed = computed(() => data.value.map(item => heavyTransform(item)))
6.2 遅延読み込み
// ❌ BAD - Loads everything upfront
import { HeavyChart } from '@/components/HeavyChart'
// ✅ GOOD - Lazy load heavy components
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => import('@/components/HeavyChart'))
6.3 デバウンス/スロットリング
// ❌ BAD - API call on every keystroke
const handleSearch = (q: string) => fetchResults(q)
// ✅ GOOD - Debounced search (300ms delay)
import { useDebounceFn } from '@vueuse/core'
const debouncedSearch = useDebounceFn((q: string) => fetchResults(q), 300)
6.4 効率的なデータ構造
// ❌ BAD - O(n) lookup
const user = users.find(u => u.id === id)
// ✅ GOOD - O(1) lookup with Map
const userMap = new Map(users.map(u => [u.id, u]))
const user = userMap.get(id)
// ✅ GOOD - O(1) membership check with Set
const allowed = new Set(['read', 'write'])
const hasAccess = allowed.has(permission)
6.5 非同期処理の並列実行
// ❌ BAD - Sequential (total = sum of times)
const user = await fetchUser()
const metrics = await fetchMetrics()
// ✅ GOOD - Parallel (total = max of times)
const [user, metrics] = await Promise.all([fetchUser(), fetchMetrics()])
// ✅ GOOD - With error handling
const results = await Promise.allSettled([fetchUser(), fetchMetrics()])
7. セキュリティ基準
7.1 既知の脆弱性
TypeScript 自体には優れたセキュリティ実績があります。主なリスクは次のとおりです。
| リスク領域 | 説明 | 軽減策 |
|---|---|---|
| 型消去 | 型は実行時には存在しません | 外部データの検証には Zod を使用します |
| Any 型 | 型チェックを無効にします | noImplicitAny を有効にし、unknown を使用します |
| 型アサーション | 型システムを回避できます | as を避け、代わりに型ガードを使用します |
7.2 OWASP Top 10 への対応
| OWASP カテゴリー | TypeScript による軽減策 |
|---|---|
| A03 インジェクション | 型付き API によって、クエリーでの文字列補間を防ぎます |
| A04 安全でない設計 | 強い型付けによって、安全なインターフェースを適用します |
| A08 ソフトウェアの完全性 | コンパイル時チェックによって、デプロイ前にエラーを検出します |
7.3 安全な型パターン
// ❌ DANGEROUS - Type assertion bypasses safety
const userData = apiResponse as User
// ✅ SECURE - Runtime validation
const userData = userSchema.parse(apiResponse)
// ❌ DANGEROUS - any disables all checks
function process(data: any) { /* ... */ }
// ✅ SECURE - unknown requires narrowing
function process(data: unknown) {
const validated = commandSchema.parse(data)
// Now safely typed
}
8. テストと品質
// Type testing with vitest
import { expectTypeOf } from 'vitest'
describe('Type Safety', () => {
it('should enforce branded types', () => {
expectTypeOf(createUserId('usr_1234567890123456')).toEqualTypeOf<UserId>()
})
})
// Schema validation tests
describe('Command Schema', () => {
it('should reject invalid IDs', () => {
expect(() => commandSchema.parse({ id: 'invalid' })).toThrow()
})
it('should accept valid commands', () => {
const result = commandSchema.parse({ id: 'cmd_1234567890123456', action: 'query' })
expect(result.action).toBe('query')
})
})
9. よくある誤りとアンチパターン
9.1 重大なセキュリティ上のアンチパターン
禁止:外部データに型アサーションを使用する
// ❌ DANGEROUS - No runtime validation
const user = JSON.parse(data) as User
// ✅ SECURE - Validate at runtime
const user = userSchema.parse(JSON.parse(data))
禁止:Null/Undefined のチェックを無視する
// ❌ DANGEROUS - Runtime crash if undefined
function getConfig(key: string) {
return config[key].value // May be undefined!
}
// ✅ SECURE - Explicit null handling
function getConfig(key: string): string | undefined {
return config[key]?.value
}
禁止:チェックせずにインデックスシグネチャを使用する
// ❌ DANGEROUS - No type safety
const handlers: Record<string, Handler> = {}
handlers['cmd'].execute() // May be undefined!
// ✅ SECURE - Explicit lookup with check
const handler = handlers['cmd']
if (handler) {
handler.execute()
}
9.2 パフォーマンス上のアンチパターン
避ける:型を過度に複雑にする
// ❌ BAD - Unreadable, slow compilation
type DeepPartialRecord<T> = T extends object
? { [K in keyof T]?: DeepPartialRecord<T[K]> }
: T extends Array<infer U>
? Array<DeepPartialRecord<U>>
: T
// ✅ GOOD - Simple, clear types
interface PartialConfig {
theme?: Partial<ThemeConfig>
metrics?: Partial<MetricsConfig>
}
10. デプロイ前チェックリスト
フェーズ 1:コードを書く前
- 先に失敗するテストを書く(TDD)
- 新機能の型とインターフェースを定義する
- 外部データ用の Zod スキーマを計画する
- パフォーマンスが重要な処理経路を特定する
- コードベースの既存のパターンを確認する
フェーズ 2:実装中
- tsconfig で
strict: trueを有効にする -
noUncheckedIndexedAccess: trueを有効にする -
noImplicitAny: trueを有効にする - 本番コードで
any型を使用しない - すべての外部データを Zod で検証する
- 機密性の高い ID にはブランド型を使う
- 状態機械には判別可能なユニオンを使う
- コストの高い計算にメモ化を適用する
- 重いコンポーネントには遅延読み込みを使う
- 検索には効率的なデータ構造(Map/Set)を使う
フェーズ 3:コミット前
- すべてのテストが成功する(
npx vitest run) - lint エラーがない(
npx eslint .) - TypeScript エラーがない(
npx tsc --noEmit) - すべての API 入力を実行時に検証する
- 外部データに対する型アサーションを使用しない
- 必要な箇所にパフォーマンスパターンを適用する
11. まとめ
TypeScript は、JARVIS の開発にコンパイル時の安全性をもたらします。
- 厳格な設定:最大限の安全性を確保するため、すべての strict オプションを有効にします
- 実行時の検証:型は実行時には消えるため、Zod を使います
- ブランド型:ID や機密性の高い値の混同を防ぎます
- 型ガード:述語を使って
unknownデータを安全に絞り込みます
覚えておいてください:TypeScript がチェックするのはコンパイル時のみです。外部データとのすべての境界で、実行時の検証が必要です。
参考資料:
references/advanced-patterns.md- 複雑な型のパターンreferences/security-examples.md- 安全な型付けの実践例
コメント
Markdown 対応