pinia by martinholovsky/claude-skills-generator
npx skills add https://github.com/martinholovsky/claude-skills-generator --skill pinia文件组织:此技能采用拆分结构。高级模式和安全示例请参阅
references/目录。
此技能为 JARVIS AI 助手提供 Pinia 专业知识,用于管理应用程序状态,包括系统指标、用户偏好和 HUD 配置。
风险等级:中等 - 管理敏感状态,需考虑 SSR,存在潜在数据暴露风险
主要用例:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 包 | 版本 | 备注 |
|---|
| pinia | ^2.1.0 | 最新稳定版 |
| @pinia/nuxt | ^0.5.0 | Nuxt 集成 |
| pinia-plugin-persistedstate | ^3.0.0 | 可选持久化 |
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
storesDirs: ['./stores/**']
}
})
为每个存储遵循此工作流:
步骤 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:遵循模式进行重构
步骤 4:运行完整验证
npm run test -- --filter=stores
npm run typecheck
npm run build
// 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
}
})
// 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 密钥
}
})
// 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 }
})
<script setup lang="ts">
// ✅ SSR 安全 - 存储按请求初始化
const jarvisStore = useJarvisStore()
// ✅ 在服务器上获取数据
const { data } = await useFetch('/api/metrics')
// 使用获取的数据更新存储
if (data.value) {
jarvisStore.updateMetrics(data.value)
}
</script>
// stores/dashboard.ts
export const useDashboardStore = defineStore('dashboard', () => {
// ✅ 从其他存储组合
const jarvisStore = useJarvisStore()
const commandStore = useCommandStore()
const dashboardStatus = computed(() => ({
systemHealth: jarvisStore.systemHealth,
pendingCommands: commandStore.pendingCommands.length,
isActive: jarvisStore.isActive
}))
return {
dashboardStatus
}
})
| OWASP 类别 | 风险 | 缓解措施 |
|---|---|---|
| A01 访问控制失效 | 中等 | 验证操作,检查权限 |
| A04 不安全的设计 | 中等 | SSR 状态隔离 |
| A07 身份验证和授权失败 | 中等 | 切勿持久化令牌 |
// ❌ 切勿持久化:令牌、API 密钥、密码
// ✅ 仅将敏感数据存储在内存中(无持久化选项)
const authStore = defineStore('auth', () => {
const token = ref<string | null>(null)
return { token }
})
// 错误 - 订阅整个存储
const store = useJarvisStore()
watch(() => store.state, () => { /* ... */ }, { deep: true })
// 正确 - 订阅特定属性
const store = useJarvisStore()
watch(() => store.state.status, (newStatus) => {
console.log('Status changed:', newStatus)
})
// 错误 - 每次访问都重新计算
function getFilteredItems() {
return items.value.filter(i => i.active)
}
// 正确 - 缓存直到依赖项改变
const filteredItems = computed(() =>
items.value.filter(i => i.active)
)
// 错误 - 多次响应式触发
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() }
}
// 错误 - 存储立即初始化
const heavyStore = useHeavyDataStore()
// 正确 - 仅在需要时初始化
const heavyStore = ref<ReturnType<typeof useHeavyDataStore> | null>(null)
function loadHeavyData() {
if (!heavyStore.value) {
heavyStore.value = useHeavyDataStore()
}
return heavyStore.value
}
// 错误 - 等待服务器响应
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
}
}
完整的测试驱动开发工作流及 vitest 示例,请参阅 第 3.3 节。
// ❌ 全局状态在 SSR 用户间泄漏
const state = reactive({ user: null })
// ✅ Pinia 按请求隔离
export const useUserStore = defineStore('user', () => {
const user = ref(null)
return { user }
})
// ❌ 切勿持久化身份验证令牌(XSS 风险)
persist: { paths: ['authToken'] }
// ✅ 对身份验证使用 httpOnly cookies
带有正确/错误示例的详细性能模式,请参阅 第 5.5 节。
npm run test -- --filter=storesnpm run typechecknpm run buildPinia 为 JARVIS 提供类型安全的状态管理:
参考资料:高级模式和安全示例请参阅 references/ 目录。
每周安装数
77
代码仓库
GitHub 星标数
30
首次出现
2026 年 1 月 20 日
安全审计
安装于
gemini-cli62
codex59
opencode59
github-copilot56
cursor55
claude-code51
File Organization : This skill uses split structure. See
references/for advanced patterns and security examples.
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 :
| Package | Version | Notes |
|---|---|---|
| pinia | ^2.1.0 | Latest stable |
| @pinia/nuxt | ^0.5.0 | Nuxt integration |
| pinia-plugin-persistedstate | ^3.0.0 | Optional persistence |
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@pinia/nuxt'],
pinia: {
storesDirs: ['./stores/**']
}
})
Follow this workflow for every store:
Step 1: Write Failing Test First
// 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
// 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
Step 4: Run Full Verification
npm run test -- --filter=stores
npm run typecheck
npm run build
// 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
}
})
// 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
}
})
// 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 }
})
<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>
// 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
}
})
| OWASP Category | Risk | Mitigation |
|---|---|---|
| A01 Broken Access Control | MEDIUM | Validate actions, check permissions |
| A04 Insecure Design | MEDIUM | SSR state isolation |
| A07 Auth Failures | MEDIUM | Never persist tokens |
// ❌ 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 }
})
// 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)
})
// 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)
)
// 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() }
}
// 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
}
// 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
}
}
See Section 3.3 for complete TDD workflow with vitest examples.
// ❌ 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
See Section 5.5 for detailed performance patterns with Good/Bad examples.
npm run test -- --filter=storesnpm run typechecknpm run buildPinia provides type-safe state management for JARVIS:
References : See references/ for advanced patterns and security examples.
Weekly Installs
77
Repository
GitHub Stars
30
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
gemini-cli62
codex59
opencode59
github-copilot56
cursor55
claude-code51
测试策略完整指南:单元/集成/E2E测试金字塔与自动化实践
11,200 周安装