Intuitive, type safe and flexible Store for Vue
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Functions for defining stores with both composition API (setup function) and options API syntax, providing flexible patterns for different development preferences.
Creates a store definition using the options API syntax with explicit state, getters, and actions properties.
/**
* Creates a store definition using options API syntax
* @param id - Unique identifier for the store
* @param options - Store configuration with state, getters, and actions
* @returns Store definition function that creates store instances
*/
function defineStore<
Id extends string,
S extends StateTree = {},
G extends _GettersTree<S> = {},
A = {}
>(
id: Id,
options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
): StoreDefinition<Id, S, G, A>;
interface DefineStoreOptions<Id extends string, S extends StateTree, G extends _GettersTree<S>, A> {
/** Unique store identifier */
id: Id;
/** Function that returns the initial state */
state?: () => S;
/** Object containing getter functions */
getters?: G & _GettersTree<S>;
/** Object containing action methods */
actions?: A & _ActionsTree;
/** Hydration function for SSR */
hydrate?(storeState: UnwrapRef<S>, initialState: UnwrapRef<S>): void;
}Usage Examples:
import { defineStore } from "pinia";
// Basic options store
const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
name: "Counter",
}),
getters: {
doubleCount: (state) => state.count * 2,
displayName: (state) => `${state.name}: ${state.count}`,
},
actions: {
increment() {
this.count++;
},
setName(newName: string) {
this.name = newName;
},
async fetchInitialData() {
const response = await fetch("/api/counter");
const data = await response.json();
this.count = data.count;
},
},
});
// Store with complex state and getters
const useUserStore = defineStore("user", {
state: () => ({
users: [] as User[],
currentUserId: null as string | null,
loading: false,
}),
getters: {
currentUser: (state) =>
state.users.find(user => user.id === state.currentUserId),
activeUsers: (state) =>
state.users.filter(user => user.active),
getUserById: (state) => {
return (userId: string) => state.users.find(user => user.id === userId);
},
},
actions: {
async loadUsers() {
this.loading = true;
try {
const users = await fetchUsers();
this.users = users;
} finally {
this.loading = false;
}
},
setCurrentUser(userId: string) {
this.currentUserId = userId;
},
},
});Creates a store definition using the composition API syntax with a setup function that returns the store's reactive state, computed properties, and methods.
/**
* Creates a store definition using composition API setup function
* @param id - Unique identifier for the store
* @param storeSetup - Setup function that defines the store logic
* @param options - Additional options for the setup store
* @returns Store definition function that creates store instances
*/
function defineStore<Id extends string, SS>(
id: Id,
storeSetup: () => SS,
options?: DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
): StoreDefinition<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>;
interface DefineSetupStoreOptions<Id extends string, S extends StateTree, G, A> {
/** Hydration function for SSR */
hydrate?(storeState: UnwrapRef<S>, initialState: UnwrapRef<S>): void;
/** Actions that should be serialized for devtools */
actions?: Record<string, any>;
}Usage Examples:
import { defineStore } from "pinia";
import { ref, computed } from "vue";
// Basic setup store
const useCounterStore = defineStore("counter", () => {
// State (equivalent to state in options API)
const count = ref(0);
const name = ref("Counter");
// Getters (equivalent to getters in options API)
const doubleCount = computed(() => count.value * 2);
const displayName = computed(() => `${name.value}: ${count.value}`);
// Actions (equivalent to actions in options API)
function increment() {
count.value++;
}
function setName(newName: string) {
name.value = newName;
}
async function fetchInitialData() {
const response = await fetch("/api/counter");
const data = await response.json();
count.value = data.count;
}
// Return all state, getters, and actions
return {
count,
name,
doubleCount,
displayName,
increment,
setName,
fetchInitialData,
};
});
// Advanced setup store with complex logic
const useTaskStore = defineStore("tasks", () => {
const tasks = ref<Task[]>([]);
const filter = ref<"all" | "active" | "completed">("all");
const loading = ref(false);
// Computed properties
const filteredTasks = computed(() => {
switch (filter.value) {
case "active":
return tasks.value.filter(task => !task.completed);
case "completed":
return tasks.value.filter(task => task.completed);
default:
return tasks.value;
}
});
const taskStats = computed(() => ({
total: tasks.value.length,
active: tasks.value.filter(t => !t.completed).length,
completed: tasks.value.filter(t => t.completed).length,
}));
// Actions
async function loadTasks() {
loading.value = true;
try {
const response = await fetch("/api/tasks");
tasks.value = await response.json();
} finally {
loading.value = false;
}
}
function addTask(title: string) {
tasks.value.push({
id: Date.now().toString(),
title,
completed: false,
createdAt: new Date(),
});
}
function toggleTask(id: string) {
const task = tasks.value.find(t => t.id === id);
if (task) {
task.completed = !task.completed;
}
}
function setFilter(newFilter: typeof filter.value) {
filter.value = newFilter;
}
return {
// State
tasks: readonly(tasks),
filter,
loading,
// Getters
filteredTasks,
taskStats,
// Actions
loadTasks,
addTask,
toggleTask,
setFilter,
};
});Setup stores can include additional options for hydration and action configuration.
const useAdvancedStore = defineStore("advanced", () => {
const data = ref<any>(null);
function updateData(newData: any) {
data.value = newData;
}
return { data, updateData };
}, {
// Custom hydration logic for SSR
hydrate(storeState, initialState) {
storeState.data = initialState.data;
},
});type StoreDefinition<Id extends string = string, S extends StateTree = {}, G = {}, A = {}> =
() => Store<Id, S, G, A>;
type _GettersTree<S extends StateTree> = Record<
string,
((state: UnwrapRef<S> & UnwrapRef<PiniaCustomStateProperties>) => any) | (() => any)
>;
type _ActionsTree = Record<string, _Method>;
type _Method = (...args: any[]) => any;
// Extraction types for setup stores
type _ExtractStateFromSetupStore<SS> = SS extends undefined | void
? {}
: _ExtractStateFromSetupStore_Keys<SS> extends keyof SS
? Pick<SS, _ExtractStateFromSetupStore_Keys<SS>>
: never;
type _ExtractGettersFromSetupStore<SS> = SS extends undefined | void
? {}
: _ExtractGettersFromSetupStore_Keys<SS> extends keyof SS
? Pick<SS, _ExtractGettersFromSetupStore_Keys<SS>>
: never;
type _ExtractActionsFromSetupStore<SS> = SS extends undefined | void
? {}
: _ExtractActionsFromSetupStore_Keys<SS> extends keyof SS
? Pick<SS, _ExtractActionsFromSetupStore_Keys<SS>>
: never;Install with Tessl CLI
npx tessl i tessl/npm-pinia