Intuitive, type safe and flexible Store for Vue
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Map helper functions that provide compatibility with Vue's Options API, allowing stores to be mapped to component computed properties and methods.
Maps multiple stores to component computed properties, providing access to store instances via this.
/**
* Maps multiple stores to component computed properties
* @param stores - Array of store definitions to map
* @returns Object with store accessor computed properties
*/
function mapStores<Stores extends any[]>(...stores: [...Stores]): _Spread<Stores>;
type _Spread<A extends readonly any[]> = A extends readonly [infer L, ...infer R]
? _StoreObject<L> & _Spread<R>
: unknown;
type _StoreObject<S> = S extends StoreDefinition<infer Id, infer State, infer Getters, infer Actions>
? {
[K in `${Id}${MapStoresCustomization extends Record<'suffix', infer Suffix>
? Suffix
: 'Store'}`]: () => Store<Id, State, Getters, Actions>
}
: {};Usage Examples:
import { mapStores } from "pinia";
import { useUserStore } from "./stores/user";
import { useCartStore } from "./stores/cart";
import { useProductStore } from "./stores/product";
export default {
computed: {
// Maps stores to computed properties ending with "Store"
...mapStores(useUserStore, useCartStore, useProductStore),
},
methods: {
handlePurchase() {
// Access stores via computed properties
this.userStore.updateLastPurchase();
this.cartStore.clear();
this.productStore.decreaseInventory(this.cartStore.items);
},
},
};
// Custom suffix
import { setMapStoreSuffix } from "pinia";
setMapStoreSuffix("Repository");
export default {
computed: {
...mapStores(useUserStore), // Creates userRepository computed property
},
};Maps store state properties to component computed properties, providing reactive access to state values.
/**
* Maps store state properties to component computed properties
* @param useStore - Store definition to map from
* @param keys - Array of state property keys to map
* @returns Object with reactive computed properties
*/
function mapState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A,
Keys extends keyof (S & G)
>(
useStore: StoreDefinition<Id, S, G, A>,
keys: readonly Keys[]
): _MapStateReturn<S & G, Keys>;
/**
* Maps store state properties with custom names or functions
* @param useStore - Store definition to map from
* @param keysObject - Object mapping local names to store properties or functions
* @returns Object with reactive computed properties
*/
function mapState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A
>(
useStore: StoreDefinition<Id, S, G, A>,
keysObject: Record<string, keyof (S & G) | ((store: Store<Id, S, G, A>) => any)>
): _MapStateObjectReturn<Id, S, G, A>;
type _MapStateReturn<T, Keys extends keyof T> = {
[K in Keys]: () => T[K];
};
type _MapStateObjectReturn<Id extends string, S extends StateTree, G, A> = Record<
string,
() => any
>;Usage Examples:
import { mapState } from "pinia";
import { useCounterStore } from "./stores/counter";
import { useUserStore } from "./stores/user";
export default {
computed: {
// Simple array mapping
...mapState(useCounterStore, ["count", "doubleCount"]),
...mapState(useUserStore, ["currentUser", "isLoggedIn"]),
// Object mapping with custom names
...mapState(useUserStore, {
user: "currentUser",
loggedIn: "isLoggedIn",
username: (store) => store.currentUser?.name || "Guest",
}),
// Mixed usage
...mapState(useCounterStore, {
currentCount: "count",
isEven: (store) => store.count % 2 === 0,
}),
},
template: `
<div>
<p>Count: {{ count }}</p>
<p>Double: {{ doubleCount }}</p>
<p>User: {{ username }}</p>
<p>Even: {{ isEven }}</p>
</div>
`,
};Maps store state properties as writable computed properties, allowing two-way binding with v-model.
/**
* Maps store state properties as writable computed properties
* @param useStore - Store definition to map from
* @param keys - Array of state property keys to map as writable
* @returns Object with writable computed properties
*/
function mapWritableState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A,
Keys extends keyof S
>(
useStore: StoreDefinition<Id, S, G, A>,
keys: readonly Keys[]
): _MapWritableStateReturn<S, Keys>;
/**
* Maps store state properties as writable with custom names
* @param useStore - Store definition to map from
* @param keysObject - Object mapping local names to store state properties
* @returns Object with writable computed properties
*/
function mapWritableState<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A
>(
useStore: StoreDefinition<Id, S, G, A>,
keysObject: Record<string, keyof S>
): _MapWritableStateObjectReturn<S>;
type _MapWritableStateReturn<S, Keys extends keyof S> = {
[K in Keys]: {
get(): S[K];
set(value: S[K]): void;
};
};
type _MapWritableStateObjectReturn<S> = Record<string, {
get(): any;
set(value: any): void;
}>;Usage Examples:
import { mapWritableState } from "pinia";
import { useFormStore } from "./stores/form";
import { useSettingsStore } from "./stores/settings";
export default {
computed: {
// Direct writable mapping
...mapWritableState(useFormStore, ["name", "email", "message"]),
// Custom names
...mapWritableState(useSettingsStore, {
darkMode: "isDarkMode",
lang: "language",
}),
},
template: `
<form>
<!-- Two-way binding with store state -->
<input v-model="name" placeholder="Name" />
<input v-model="email" placeholder="Email" />
<textarea v-model="message" placeholder="Message"></textarea>
<!-- Settings -->
<label>
<input type="checkbox" v-model="darkMode" />
Dark Mode
</label>
<select v-model="lang">
<option value="en">English</option>
<option value="es">Spanish</option>
</select>
</form>
`,
};Maps store getters to component computed properties. This is an alias for mapState but specifically for getters.
/**
* Maps store getters to component computed properties
* @param useStore - Store definition to map from
* @param keys - Array of getter keys to map
* @returns Object with reactive computed properties
*/
function mapGetters<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A,
Keys extends keyof G
>(
useStore: StoreDefinition<Id, S, G, A>,
keys: readonly Keys[]
): _MapStateReturn<G, Keys>;Usage Examples:
import { mapGetters } from "pinia";
import { useCounterStore } from "./stores/counter";
export default {
computed: {
...mapGetters(useCounterStore, ["doubleCount", "isEven", "displayName"]),
},
template: `
<div>
<p>Double Count: {{ doubleCount }}</p>
<p>Is Even: {{ isEven ? 'Yes' : 'No' }}</p>
<p>Display: {{ displayName }}</p>
</div>
`,
};Maps store actions to component methods, providing direct access to store methods.
/**
* Maps store actions to component methods
* @param useStore - Store definition to map from
* @param keys - Array of action keys to map
* @returns Object with action methods
*/
function mapActions<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A,
Keys extends keyof A
>(
useStore: StoreDefinition<Id, S, G, A>,
keys: readonly Keys[]
): _MapActionsReturn<A, Keys>;
/**
* Maps store actions with custom names
* @param useStore - Store definition to map from
* @param keysObject - Object mapping local names to action names
* @returns Object with action methods
*/
function mapActions<
Id extends string,
S extends StateTree,
G extends _GettersTree<S>,
A
>(
useStore: StoreDefinition<Id, S, G, A>,
keysObject: Record<string, keyof A>
): _MapActionsObjectReturn<A>;
type _MapActionsReturn<A, Keys extends keyof A> = {
[K in Keys]: A[K];
};
type _MapActionsObjectReturn<A> = Record<string, any>;Usage Examples:
import { mapActions } from "pinia";
import { useCounterStore } from "./stores/counter";
import { useUserStore } from "./stores/user";
export default {
methods: {
// Direct action mapping
...mapActions(useCounterStore, ["increment", "decrement", "reset"]),
// Custom names
...mapActions(useUserStore, {
signIn: "login",
signOut: "logout",
updateProfile: "updateUser",
}),
// Use in component methods
handleIncrement() {
this.increment(); // Calls store action
},
async handleLogin() {
try {
await this.signIn({ email: this.email, password: this.password });
this.$router.push("/dashboard");
} catch (error) {
this.showError(error.message);
}
},
},
template: `
<div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
<button @click="handleLogin">Login</button>
</div>
`,
};Customizes the suffix used by mapStores when creating computed property names.
/**
* Sets the suffix for store names in mapStores
* @param suffix - New suffix to use (default is "Store")
*/
function setMapStoreSuffix(suffix: string): void;
interface MapStoresCustomization {
suffix?: string;
}Usage Examples:
import { setMapStoreSuffix, mapStores } from "pinia";
import { useUserStore } from "./stores/user";
// Default behavior
export default {
computed: {
...mapStores(useUserStore), // Creates 'userStore' computed property
},
};
// Custom suffix
setMapStoreSuffix("Repository");
export default {
computed: {
...mapStores(useUserStore), // Creates 'userRepository' computed property
},
};
// Empty suffix
setMapStoreSuffix("");
export default {
computed: {
...mapStores(useUserStore), // Creates 'user' computed property
},
};
// Reset to default
setMapStoreSuffix("Store");import { mapStores, mapState, mapWritableState, mapActions } from "pinia";
import { useCounterStore } from "./stores/counter";
import { useUserStore } from "./stores/user";
import { useCartStore } from "./stores/cart";
export default {
computed: {
// Store instances
...mapStores(useCounterStore, useUserStore, useCartStore),
// Read-only state
...mapState(useCounterStore, ["count", "doubleCount"]),
...mapState(useUserStore, ["currentUser", "isLoggedIn"]),
// Writable state for forms
...mapWritableState(useUserStore, ["email", "preferences"]),
// Custom computed
canPurchase() {
return this.isLoggedIn && this.cartStore.items.length > 0;
},
},
methods: {
// Store actions
...mapActions(useCounterStore, ["increment", "reset"]),
...mapActions(useUserStore, {
signIn: "login",
signOut: "logout",
}),
...mapActions(useCartStore, ["addItem", "removeItem", "checkout"]),
// Component methods
async handleCheckout() {
if (this.canPurchase) {
await this.checkout();
this.reset(); // Reset counter after purchase
}
},
},
template: `
<div>
<p>Count: {{ count }} ({{ doubleCount }})</p>
<button @click="increment">+</button>
<div v-if="isLoggedIn">
<p>Welcome {{ currentUser.name }}!</p>
<input v-model="email" placeholder="Email" />
<button @click="handleCheckout" :disabled="!canPurchase">
Checkout
</button>
</div>
<button v-else @click="signIn">Sign In</button>
</div>
`,
};type _GettersTree<S extends StateTree> = Record<
string,
((state: UnwrapRef<S> & UnwrapRef<PiniaCustomStateProperties>) => any) | (() => any)
>;Install with Tessl CLI
npx tessl i tessl/npm-pinia