web-state-pinia-2df5f9 — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited web-state-pinia-2df5f9 (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Quick Guide: Pinia holds shared client state in Vue 3, as flat independent stores with no modules and no mutations. Two syntaxes: Options stores read like the Options API and come with$reset(), Setup stores read like<script setup>and can call other composables. Two rules cause most of the bugs — destructure throughstoreToRefs()or reactivity is lost silently, and return every ref from a Setup store or SSR, DevTools and plugins each see a store with holes in it.
Detailed Resources:
storeToRefs, composing stores$reset for Setup stores<critical_requirements>
Destructure through `storeToRefs()`. A store is reactive but its properties are not: pulling them out directly copies values, and the component then renders numbers that never change again — with nothing thrown and nothing logged.
Return every ref a Setup store creates. A ref kept private is invisible to serialisation, to hydration, to the DevTools panel and to every plugin, so the store works locally and fails on the server.
Keep browser APIs out of state initialisation. localStorage and window do not exist while the server renders, so reading one in a state() function fails the render rather than the feature.
Create the Pinia instance per request when rendering on the server. A module-level store is a singleton, and a singleton on a server is one user's state handed to the next.
</critical_requirements>
Auto-detection: defineStore, storeToRefs, createPinia, setActivePinia, $patch, $reset, $subscribe, $onAction, pinia, Options store, Setup store
Applies to:
Handled elsewhere:
ref() or reactive() is less machinery for the same behaviourprovide/inject carries something that never changes; a store is for something that does<philosophy>
Pinia is a flat set of independent stores. There are no nested modules and no mutation layer: an action changes state directly, and the type of everything is inferred rather than declared through a registry.
The design consequence worth knowing is that a store instance belongs to a Pinia instance, not to a module. That is what makes SSR workable — a fresh Pinia per request means no state crosses between users — and it is why setActivePinia exists for code that runs outside a component.
</philosophy>
<decision_framework>
Does the store need to call another composable, or watch something?
├─ YES → Setup store — it runs in a setup context, so composables work
└─ NO → Do you want $reset() without writing it?
├─ YES → Options store — $reset() is generated from the state function
└─ NO → either; Setup store reads closest to <script setup>A Setup store can be given $reset() by a plugin, which is the usual way to have both.
Would losing this on reload annoy the user?
├─ NO → do not persist it; transient UI restored from storage reads as a bug
└─ YES → Is it a copy of something the server owns?
├─ YES → refetch it instead; a stale copy outlives its correctness
└─ NO → Is it a token, a credential or personal data?
├─ YES → do not persist it; web storage is readable by any script on the page
└─ NO → persist that field by name, not the whole storeSession-scoped state — a cart, a form draft — belongs in sessionStorage rather than localStorage, so closing the tab ends it.
</decision_framework>
<patterns>
State, getters and actions as three declared sections. Getters take state; actions use this.
export const useCounterStore = defineStore("counter", {
state: (): CounterState => ({ count: 0, name: "Counter" }),
getters: {
doubleCount: (state): number => state.count * 2,
},
actions: {
increment(): void {
this.count++;
},
},
});Full code: examples/core.md
ref() is state, computed() is a getter, a function is an action — and the returned object is the store.
export const useCounterStore = defineStore("counter", () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment(): void {
count.value++;
}
return { count, doubleCount, increment };
});Everything created here is returned. A ref left out is not private — it is broken for anything that reads the store from outside.
Full code: examples/core.md
const store = useCounterStore();
// state and getters — through storeToRefs
const { count, doubleCount } = storeToRefs(store);
// actions — destructured directly; they are functions and need no ref
const { increment } = store;storeToRefs covers state and getters only. Passing it actions is what the direct destructure on the second line is for.
Full code: examples/core.md
One store calls another at the top of its body, so the dependency is resolved once rather than on each access.
export const useCartStore = defineStore("cart", () => {
const userStore = useUserStore();
const discount = computed(() => (userStore.isPremium ? PREMIUM_DISCOUNT : 0));
return { discount };
});Two stores reading each other's getters is where this deadlocks; keep the dependency one-directional.
Full code: examples/core.md
A plugin syncs chosen fields to storage. Naming the fields is what keeps a transient flag or a cached response out of it.
Full code: examples/persistence.md
A plugin receives every store and can add a property, wrap an action, or read a custom option declared on the store.
pinia.use(({ store, options }) => {
store.$onAction(({ name, after }) => {
after(() => {
/* record the call */
});
});
});This is the mechanism behind $reset() for Setup stores and behind persistence alike.
Full code: examples/plugins.md
setActivePinia(createPinia()) gives a unit test a store with no component around it. A testing Pinia stubs actions by default, so a component test asserts that an action was called rather than what it did.
Full code: examples/testing.md
A fresh Pinia per request, no browser API in the initial state, and serialised state sanitised before it reaches the page.
Full code: examples/ssr.md
</patterns>
<red_flags>
Breaks at runtime:
storeToRefs() is the fix.Surprising behaviour:
$reset(); Setup stores do not, because there is no state function to re-run. A plugin supplies one.this, so a getter written that way reads the state parameter or nothing at all.$patch takes an object or a function; the function form receives the current state, which is what makes an array push or a conditional update expressible.setActivePinia in tests and in scripts, or the call throws.</red_flags>
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.