mcpField
P
pinia

pinia

github.com/martinholovsky開発48197 回インストール48 週間前に更新
リポジトリ

pinia について

JARVIS AI アシスタントを開発する方向けの Pinia 状態管理スキルです。型付きストア、SSR の分離、安全な永続化、効率的なリアクティブ更新を扱います。

主な機能

  1. 01TypeScript による Setup Store
  2. 02SSR リクエスト間の状態分離
  3. 03機密データの永続化防止
  4. 04ストアアクションの入力検証
  5. 05算出値と選択的サブスクリプション
  6. 06Vitest によるストアテスト

活用シーン

  1. 01システムメトリクスと状態の追跡
  2. 02設定と HUD 構成の管理
  3. 03コマンド履歴とキューの保持
  4. 04アプリケーションのリアルタイムデータ同期

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill pinia

インストール後、Claude Code で/piniaを入力して読み込まれたか確認します。

同梱ファイルreferences/advanced-patterns.mdreferences/security-examples.md

詳細

Pinia 状態管理スキル

ファイル構成: このスキルは分割構成を使用します。高度なパターンやセキュリティの例については、references/ を参照してください。

1. 概要

このスキルでは、JARVIS AI アシスタントでアプリケーションの状態を管理するための Pinia の知識を提供します。対象には、システムメトリクス、ユーザー設定、HUD 構成が含まれます。

リスクレベル: MEDIUM - 機密性の高い状態、SSR に関する考慮事項、データ漏えいの可能性を管理します

主なユースケース:

  • システムメトリクスとステータスの追跡
  • ユーザー設定と環境設定
  • HUD 構成の状態
  • コマンド履歴とキュー
  • リアルタイムのデータ同期

2. 主な責務

2.1 基本原則
  1. TDD を優先: 実装の前にストアのテストを作成します
  2. パフォーマンスを考慮: サブスクリプションと算出値を最適化します
  3. 型安全性: TypeScript の完全な型付けでストアを定義します
  4. SSR のセキュリティ: リクエスト間での状態漏えいを防ぎます
  5. Composition API: TypeScript のサポートを向上させるため、セットアップストアを使用します
  6. 状態の最小化: 必要なデータのみを保存し、残りは導出します
  7. アクションの検証: 変更前にアクション内で入力を検証します
  8. 永続化のセキュリティ: 機密データを localStorage に永続化しないでください

3. 技術スタックとバージョン

3.1 推奨バージョン
パッケージバージョン備考
pinia^2.1.0最新の安定版
@pinia/nuxt^0.5.0Nuxt との統合
pinia-plugin-persistedstate^3.0.0永続化(任意)
3.2 Nuxt の設定
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt'],
  pinia: {
    storesDirs: ['./stores/**']
  }
})
3.3 実装ワークフロー(TDD)

すべてのストアで、次のワークフローに従ってください。

ステップ 1: 失敗するテストを先に作成

// tests/stores/metrics.test.ts
import { describe, it, expect, beforeEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useMetricsStore } from '~/stores/metrics'

describe('MetricsStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('should initialize with default values', () => {
    const store = useMetricsStore()
    expect(store.cpu).toBe(0)
    expect(store.memory).toBe(0)
  })

  it('should clamp values within valid range', () => {
    const store = useMetricsStore()
    store.updateCpu(150)
    expect(store.cpu).toBe(100)
    store.updateCpu(-50)
    expect(store.cpu).toBe(0)
  })

  it('should compute health status correctly', () => {
    const store = useMetricsStore()
    store.updateCpu(95)
    store.updateMemory(90)
    expect(store.healthStatus).toBe('critical')
  })
})

ステップ 2: テストに合格するための最小限の実装

// stores/metrics.ts
export const useMetricsStore = defineStore('metrics', () => {
  const cpu = ref(0)
  const memory = ref(0)

  const healthStatus = computed(() => {
    const avg = (cpu.value + memory.value) / 2
    if (avg > 90) return 'critical'
    if (avg > 70) return 'warning'
    return 'healthy'
  })

  function updateCpu(value: number) {
    cpu.value = Math.max(0, Math.min(100, value))
  }

  function updateMemory(value: number) {
    memory.value = Math.max(0, Math.min(100, value))
  }

  return { cpu, memory, healthStatus, updateCpu, updateMemory }
})

ステップ 3: パターンに従ってリファクタリング

  • 検証ロジックを切り出します
  • TypeScript のインターフェースを追加します
  • 算出値の依存関係を最適化します

ステップ 4: 全体の検証を実行

npm run test -- --filter=stores
npm run typecheck
npm run build

4. 実装パターン

4.1 TypeScript を使用したセットアップストア
// stores/jarvis.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

interface SystemMetrics {
  cpu: number
  memory: number
  network: number
  timestamp: number
}

interface JARVISState {
  status: 'idle' | 'listening' | 'processing' | 'responding'
  securityLevel: 'normal' | 'elevated' | 'lockdown'
}

export const useJarvisStore = defineStore('jarvis', () => {
  // State
  const state = ref<JARVISState>({
    status: 'idle',
    securityLevel: 'normal'
  })

  const metrics = ref<SystemMetrics>({
    cpu: 0,
    memory: 0,
    network: 0,
    timestamp: Date.now()
  })

  // Getters
  const isActive = computed(() =>
    state.value.status !== 'idle'
  )

  const systemHealth = computed(() => {
    const avg = (metrics.value.cpu + metrics.value.memory) / 2
    if (avg > 90) return 'critical'
    if (avg > 70) return 'warning'
    return 'healthy'
  })

  // Actions
  function updateMetrics(newMetrics: Partial<SystemMetrics>) {
    // ✅ Validate input
    if (newMetrics.cpu !== undefined) {
      metrics.value.cpu = Math.max(0, Math.min(100, newMetrics.cpu))
    }
    if (newMetrics.memory !== undefined) {
      metrics.value.memory = Math.max(0, Math.min(100, newMetrics.memory))
    }
    if (newMetrics.network !== undefined) {
      metrics.value.network = Math.max(0, newMetrics.network)
    }
    metrics.value.timestamp = Date.now()
  }

  function setStatus(newStatus: JARVISState['status']) {
    state.value.status = newStatus
  }

  function setSecurityLevel(level: JARVISState['securityLevel']) {
    state.value.securityLevel = level

    // ✅ Audit security changes
    console.info(`Security level changed to: ${level}`)
  }

  return {
    state,
    metrics,
    isActive,
    systemHealth,
    updateMetrics,
    setStatus,
    setSecurityLevel
  }
})
4.2 ユーザー設定ストア(永続化あり)
// stores/preferences.ts
export const usePreferencesStore = defineStore('preferences', () => {
  const preferences = ref({
    theme: 'dark' as 'dark' | 'light',
    hudOpacity: 0.8,
    soundEnabled: true
  })

  function updatePreference<K extends keyof typeof preferences.value>(
    key: K, value: typeof preferences.value[K]
  ) {
    if (key === 'hudOpacity' && (value < 0 || value > 1)) return
    preferences.value[key] = value
  }

  return { preferences, updatePreference }
}, {
  persist: {
    key: 'jarvis-preferences',
    paths: ['preferences.theme', 'preferences.hudOpacity']
    // ❌ Never persist: tokens, passwords, API keys
  }
})
4.3 コマンドキューストア
// stores/commands.ts
interface Command {
  id: string
  action: string
  status: 'pending' | 'executing' | 'completed' | 'failed'
}

export const useCommandStore = defineStore('commands', () => {
  const queue = ref<Command[]>([])
  const history = ref<Command[]>([])
  const MAX_HISTORY = 100

  const pendingCommands = computed(() =>
    queue.value.filter(cmd => cmd.status === 'pending')
  )

  function addCommand(action: string) {
    const cmd: Command = { id: crypto.randomUUID(), action, status: 'pending' }
    queue.value.push(cmd)
    return cmd.id
  }

  function completeCommand(id: string, status: 'completed' | 'failed') {
    const idx = queue.value.findIndex(cmd => cmd.id === id)
    if (idx !== -1) {
      const [cmd] = queue.value.splice(idx, 1)
      cmd.status = status
      history.value = [cmd, ...history.value].slice(0, MAX_HISTORY)
    }
  }

  return { queue, history, pendingCommands, addCommand, completeCommand }
})
4.4 SSR に安全なストアの使用
<script setup lang="ts">
// ✅ Safe for SSR - store initialized per-request
const jarvisStore = useJarvisStore()

// ✅ Fetch data on server
const { data } = await useFetch('/api/metrics')

// Update store with fetched data
if (data.value) {
  jarvisStore.updateMetrics(data.value)
}
</script>
4.5 ストアの合成
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', () => {
  // ✅ Compose from other stores
  const jarvisStore = useJarvisStore()
  const commandStore = useCommandStore()

  const dashboardStatus = computed(() => ({
    systemHealth: jarvisStore.systemHealth,
    pendingCommands: commandStore.pendingCommands.length,
    isActive: jarvisStore.isActive
  }))

  return {
    dashboardStatus
  }
})

5. セキュリティ基準

5.1 OWASP 対応
OWASP カテゴリーリスク緩和策
A01 アクセス制御の不備MEDIUMアクションを検証し、権限を確認します
A04 安全でない設計MEDIUMSSR の状態を分離します
A07 認証の失敗MEDIUMトークンを永続化しないでください
5.3 機密データの取り扱い
// ❌ NEVER persist: tokens, API keys, passwords
// ✅ Store sensitive data in memory only (no persist option)
const authStore = defineStore('auth', () => {
  const token = ref<string | null>(null)
  return { token }
})

5.5 パフォーマンスパターン

パターン 1: 選択的なサブスクリプション
// BAD - Subscribes to entire store
const store = useJarvisStore()
watch(() => store.state, () => { /* ... */ }, { deep: true })

// GOOD - Subscribe to specific properties
const store = useJarvisStore()
watch(() => store.state.status, (newStatus) => {
  console.log('Status changed:', newStatus)
})
パターン 2: 算出ゲッター(メモ化)
// BAD - Recalculates on every access
function getFilteredItems() {
  return items.value.filter(i => i.active)
}

// GOOD - Cached until dependencies change
const filteredItems = computed(() =>
  items.value.filter(i => i.active)
)
パターン 3: 更新の一括処理
// BAD - Multiple reactive triggers
function updateAll(data: MetricsData) {
  metrics.value.cpu = data.cpu
  metrics.value.memory = data.memory
  metrics.value.network = data.network
}

// GOOD - Single reactive trigger
function updateAll(data: MetricsData) {
  metrics.value = { ...metrics.value, ...data, timestamp: Date.now() }
}
パターン 4: ストアの遅延初期化
// BAD - Store initializes immediately
const heavyStore = useHeavyDataStore()

// GOOD - Initialize only when needed
const heavyStore = ref<ReturnType<typeof useHeavyDataStore> | null>(null)

function loadHeavyData() {
  if (!heavyStore.value) {
    heavyStore.value = useHeavyDataStore()
  }
  return heavyStore.value
}
パターン 5: 楽観的更新
// BAD - Wait for server response
async function deleteItem(id: string) {
  await api.delete(`/items/${id}`)
  items.value = items.value.filter(i => i.id !== id)
}

// GOOD - Update immediately, rollback on error
async function deleteItem(id: string) {
  const backup = [...items.value]
  items.value = items.value.filter(i => i.id !== id)

  try {
    await api.delete(`/items/${id}`)
  } catch (error) {
    items.value = backup  // Rollback
    throw error
  }
}

6. テストと品質

vitest の例を含む TDD ワークフローの全容については、セクション 3.3 を参照してください。

8. よくあるアンチパターン

セキュリティ上のアンチパターン
// ❌ Global state leaks between SSR users
const state = reactive({ user: null })

// ✅ Pinia isolates per-request
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  return { user }
})

// ❌ Never persist auth tokens (XSS risk)
persist: { paths: ['authToken'] }

// ✅ Use httpOnly cookies for auth
パフォーマンス上のアンチパターン

Good/Bad の例を含む詳細なパフォーマンスパターンについては、セクション 5.5 を参照してください。

13. 実装前チェックリスト

フェーズ 1: コードを書く前
  • TypeScript の型を使用してストアのインターフェースを設計済み
  • 失敗するテストを含むテストファイルを作成済み
  • セキュリティ要件(永続化、SSR)を特定済み
  • ユースケースに適したパフォーマンスパターンを選定済み
フェーズ 2: 実装中
  • 機能を追加するたびにテストが合格
  • すべての入力をアクションで検証
  • 算出値の依存関係を最小限に抑制
  • 永続化する状態に機密データがない
  • SSR の状態を適切に分離
フェーズ 3: コミット前
  • すべてのストアテストが合格: npm run test -- --filter=stores
  • 型チェックが合格: npm run typecheck
  • ビルドが成功: npm run build
  • Pinia の外部にグローバル状態がない
  • 状態の形状を型で文書化済み

14. まとめ

Pinia は JARVIS に型安全な状態管理を提供します。

  1. TDD を最優先:実装の前にストアのテストを作成します
  2. パフォーマンス:サブスクリプションと算出値を最適化します
  3. セキュリティ:機密データは決して永続化せず、SSR の状態を分離します
  4. 型安全性:TypeScript を全面的に活用できるセットアップストアを使用します

参考資料:高度なパターンとセキュリティの例については、references/ を参照してください。

コメント