pinia

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Pinia State Management Skill

Pinia状态管理技能

File Organization: This skill uses split structure. See
references/
for advanced patterns and security examples.
文件组织:本技能采用拆分结构。高级模式和安全示例请查看
references/
目录。

1. Overview

1. 概述

This skill provides Pinia expertise for managing application state in the JARVIS AI Assistant, including system metrics, user preferences, and HUD configuration.
Risk Level: MEDIUM - Manages sensitive state, SSR considerations, potential data exposure
Primary Use Cases:
  • System metrics and status tracking
  • User preferences and settings
  • HUD configuration state
  • Command history and queue
  • Real-time data synchronization
本技能为JARVIS AI助手提供Pinia状态管理能力,涵盖系统指标、用户偏好和HUD配置等场景。
风险等级:中等 - 管理敏感状态,需考虑SSR场景,存在数据泄露风险
主要使用场景:
  • 系统指标与状态追踪
  • 用户偏好与设置管理
  • HUD配置状态维护
  • 命令历史与队列管理
  • 实时数据同步

2. Core Responsibilities

2. 核心职责

2.1 Core Principles

2.1 核心原则

  1. TDD First: Write store tests before implementation
  2. Performance Aware: Optimize subscriptions and computed values
  3. Type Safety: Define stores with full TypeScript typing
  4. SSR Security: Prevent state leakage between requests
  5. Composition API: Use setup stores for better TypeScript support
  6. Minimal State: Store only necessary data, derive the rest
  7. Action Validation: Validate inputs in actions before mutations
  8. Persistence Security: Never persist sensitive data to localStorage
  1. TDD优先:在实现前编写Store测试用例
  2. 性能感知:优化订阅与计算属性
  3. 类型安全:使用完整TypeScript类型定义Store
  4. SSR安全:防止请求间的状态泄露
  5. 组合式API:使用Setup Store以获得更好的TypeScript支持
  6. 最小化状态:仅存储必要数据,其余数据通过推导获取
  7. 动作校验:在修改状态前校验动作输入
  8. 持久化安全:绝不要将敏感数据持久化到localStorage

3. Technology Stack & Versions

3. 技术栈与版本

3.1 Recommended Versions

3.1 推荐版本

PackageVersionNotes
pinia^2.1.0Latest stable
@pinia/nuxt^0.5.0Nuxt integration
pinia-plugin-persistedstate^3.0.0Optional persistence
PackageVersion说明
pinia^2.1.0最新稳定版
@pinia/nuxt^0.5.0Nuxt集成包
pinia-plugin-persistedstate^3.0.0可选持久化插件

3.2 Nuxt Configuration

3.2 Nuxt配置

typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt'],
  pinia: {
    storesDirs: ['./stores/**']
  }
})
typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@pinia/nuxt'],
  pinia: {
    storesDirs: ['./stores/**']
  }
})

3.3 Implementation Workflow (TDD)

3.3 实现流程(TDD)

Follow this workflow for every store:
Step 1: Write Failing Test First
typescript
// 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')
  })
})
Step 2: Implement Minimum to Pass
typescript
// 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 }
})
Step 3: Refactor Following Patterns
  • Extract validation logic
  • Add TypeScript interfaces
  • Optimize computed dependencies
Step 4: Run Full Verification
bash
npm run test -- --filter=stores
npm run typecheck
npm run build
每个Store都遵循以下流程:
步骤1:先编写失败的测试用例
typescript
// 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:实现最小代码以通过测试
typescript
// 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:运行完整验证
bash
npm run test -- --filter=stores
npm run typecheck
npm run build

4. Implementation Patterns

4. 实现模式

4.1 Setup Store with TypeScript

4.1 基于TypeScript的Setup Store

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
  }
})
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', () => {
  // 状态
  const state = ref<JARVISState>({
    status: 'idle',
    securityLevel: 'normal'
  })

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

  // 计算属性
  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'
  })

  // 动作
  function updateMetrics(newMetrics: Partial<SystemMetrics>) {
    // ✅ 校验输入
    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

    // ✅ 审计安全等级变更
    console.info(`Security level changed to: ${level}`)
  }

  return {
    state,
    metrics,
    isActive,
    systemHealth,
    updateMetrics,
    setStatus,
    setSecurityLevel
  }
})

4.2 User Preferences Store (with Persistence)

4.2 用户偏好Store(带持久化)

typescript
// 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
  }
})
typescript
// 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']
    // ❌ 绝不要持久化:令牌、密码、API密钥
  }
})

4.3 Command Queue Store

4.3 命令队列Store

typescript
// 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 }
})
typescript
// 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-Safe Store Usage

4.4 SSR安全的Store使用方式

vue
<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>
vue
<script setup lang="ts">
// ✅ SSR安全 - 每个请求初始化一次Store
const jarvisStore = useJarvisStore()

// ✅ 在服务端获取数据
const { data } = await useFetch('/api/metrics')

// 用获取到的数据更新Store
if (data.value) {
  jarvisStore.updateMetrics(data.value)
}
</script>

4.5 Store Composition

4.5 Store组合

typescript
// 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
  }
})
typescript
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', () => {
  // ✅ 从其他Store组合
  const jarvisStore = useJarvisStore()
  const commandStore = useCommandStore()

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

  return {
    dashboardStatus
  }
})

5. Security Standards

5. 安全标准

5.1 OWASP Coverage

5.1 OWASP覆盖

OWASP CategoryRiskMitigation
A01 Broken Access ControlMEDIUMValidate actions, check permissions
A04 Insecure DesignMEDIUMSSR state isolation
A07 Auth FailuresMEDIUMNever persist tokens
OWASP分类风险等级缓解措施
A01 访问控制失效中等校验动作权限
A04 不安全设计中等SSR状态隔离
A07 认证失败中等绝不持久化令牌

5.3 Sensitive Data Handling

5.3 敏感数据处理

typescript
// ❌ 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 }
})
typescript
// ❌ 绝不要持久化:令牌、API密钥、密码
// ✅ 敏感数据仅存储在内存中(不使用persist选项)
const authStore = defineStore('auth', () => {
  const token = ref<string | null>(null)
  return { token }
})

5.5 Performance Patterns

5.5 性能优化模式

Pattern 1: Selective Subscriptions

模式1:选择性订阅

typescript
// 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)
})
typescript
// 不良实践 - 订阅整个Store
const store = useJarvisStore()
watch(() => store.state, () => { /* ... */ }, { deep: true })

// 最佳实践 - 订阅特定属性
const store = useJarvisStore()
watch(() => store.state.status, (newStatus) => {
  console.log('Status changed:', newStatus)
})

Pattern 2: Computed Getters (Memoization)

模式2:计算属性(记忆化)

typescript
// 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)
)
typescript
// 不良实践 - 每次访问都重新计算
function getFilteredItems() {
  return items.value.filter(i => i.active)
}

// 最佳实践 - 缓存结果直到依赖变更
const filteredItems = computed(() =>
  items.value.filter(i => i.active)
)

Pattern 3: Batch Updates

模式3:批量更新

typescript
// 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() }
}
typescript
// 不良实践 - 多次触发响应式更新
function updateAll(data: MetricsData) {
  metrics.value.cpu = data.cpu
  metrics.value.memory = data.memory
  metrics.value.network = data.network
}

// 最佳实践 - 单次触发响应式更新
function updateAll(data: MetricsData) {
  metrics.value = { ...metrics.value, ...data, timestamp: Date.now() }
}

Pattern 4: Lazy Store Initialization

模式4:懒加载Store初始化

typescript
// 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
}
typescript
// 不良实践 - Store立即初始化
const heavyStore = useHeavyDataStore()

// 最佳实践 - 仅在需要时初始化
const heavyStore = ref<ReturnType<typeof useHeavyDataStore> | null>(null)

function loadHeavyData() {
  if (!heavyStore.value) {
    heavyStore.value = useHeavyDataStore()
  }
  return heavyStore.value
}

Pattern 5: Optimistic Updates

模式5:乐观更新

typescript
// 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
  }
}
typescript
// 不良实践 - 等待服务端响应后更新
async function deleteItem(id: string) {
  await api.delete(`/items/${id}`)
  items.value = items.value.filter(i => i.id !== id)
}

// 最佳实践 - 立即更新,错误时回滚
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  // 回滚
    throw error
  }
}

6. Testing & Quality

6. 测试与质量

See Section 3.3 for complete TDD workflow with vitest examples.
完整的TDD流程及vitest示例请查看3.3节

8. Common Anti-Patterns

8. 常见反模式

Security Anti-Patterns

安全反模式

typescript
// ❌ 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
typescript
// ❌ SSR用户间全局状态泄露
const state = reactive({ user: null })

// ✅ Pinia实现请求级状态隔离
export const useUserStore = defineStore('user', () => {
  const user = ref(null)
  return { user }
})

// ❌ 绝不要持久化认证令牌(存在XSS风险)
persist: { paths: ['authToken'] }

// ✅ 使用httpOnly Cookie处理认证

Performance Anti-Patterns

性能反模式

See Section 5.5 for detailed performance patterns with Good/Bad examples.
带好坏示例的详细性能优化模式请查看5.5节

13. Pre-Implementation Checklist

13. 实现前检查清单

Phase 1: Before Writing Code

阶段1:编写代码前

  • Store interface designed with TypeScript types
  • Test file created with failing tests
  • Security requirements identified (persistence, SSR)
  • Performance patterns selected for use case
  • 已用TypeScript类型设计Store接口
  • 已创建包含失败测试用例的测试文件
  • 已识别安全需求(持久化、SSR)
  • 已为场景选择合适的性能优化模式

Phase 2: During Implementation

阶段2:实现过程中

  • Tests passing after each feature added
  • Actions validate all inputs
  • Computed values use minimal dependencies
  • No sensitive data in persisted state
  • SSR state properly isolated
  • 新增功能后测试用例通过
  • 动作已校验所有输入
  • 计算属性使用最小依赖
  • 持久化状态中无敏感数据
  • SSR状态已正确隔离

Phase 3: Before Committing

阶段3:提交代码前

  • All store tests passing:
    npm run test -- --filter=stores
  • Type check passing:
    npm run typecheck
  • Build succeeds:
    npm run build
  • No global state outside Pinia
  • State shape documented in types
  • 所有Store测试通过:
    npm run test -- --filter=stores
  • 类型检查通过:
    npm run typecheck
  • 构建成功:
    npm run build
  • Pinia外无全局状态
  • 状态结构已在类型中记录

14. Summary

14. 总结

Pinia provides type-safe state management for JARVIS:
  1. TDD First: Write store tests before implementation
  2. Performance: Optimize subscriptions and computed values
  3. Security: Never persist sensitive data, isolate SSR state
  4. Type Safety: Use setup stores with full TypeScript
References: See
references/
for advanced patterns and security examples.
Pinia为JARVIS提供类型安全的状态管理能力:
  1. TDD优先:在实现前编写Store测试用例
  2. 性能优化:优化订阅与计算属性
  3. 安全保障:绝不持久化敏感数据,隔离SSR状态
  4. 类型安全:使用带完整TypeScript支持的Setup Store
参考资料:高级模式与安全示例请查看
references/
目录。