@ministerjs/store
Gerenciamento de estado opinado para Vue 3 + Pinia com foco em simplicidade, tipagem forte e reutilização. Fornece factories para ItemStore (estado de um único objeto) e TableStore (coleções) além de um gerenciador com cache inteligente (useStores
).
📦 Instalação
npm install @ministerjs/store
pnpm add @ministerjs/store
yarn add @ministerjs/store
🚀 Funcionalidades
- ItemStore: Estado de um item único (perfil, configurações, sessão...)
- TableStore: Coleções (listas, tabelas, catálogos) com utilidades frequentes
- Cache inteligente via
useStores
para evitar recriações
- Tipagem 100% (inferência de chaves, primary key, partial updates)
- Pinia under the hood: integra DevTools sem esforço
- Debug logging opcional (
debug: true
)
- APIs mínimas e coesas: apenas o que é realmente usado no dia a dia
📖 Uso Básico
ItemStore – Gerenciando um item único
import { createItemStore } from "@ministerjs/store";
interface UserProfile {
id: number;
name: string;
email: string;
avatar?: string;
}
const useUserProfile = createItemStore<UserProfile>({
displayName: "User Profile",
initialState: { name: "", email: "" },
debug: true,
});
const { get, set, update, clear } = useUserProfile();
const profile = get();
console.log(profile.value.name);
set({ id: 1, name: "João", email: "joao@example.com" });
update({ name: "João Silva" });
clear();
TableStore – Gerenciando coleções
import { createTableStore } from "@ministerjs/store";
interface User {
id: number;
name: string;
email: string;
active: boolean;
}
const useUsersStore = createTableStore<User, "id">({
primaryKey: "id",
displayName: "Users Store",
initialState: [],
debug: true,
});
const store = useUsersStore();
store.add({ id: 1, name: "João", email: "joao@example.com", active: true });
store.add({ id: 2, name: "Maria", email: "maria@example.com", active: false });
const user = store.get(1);
const allUsers = store.list();
const activeUser = store.getByProperty("email", "joao@example.com");
store.update(1, { name: "João Silva" });
store.remove(1);
store.replaceAll([
{ id: 3, name: "Pedro", email: "pedro@example.com", active: true },
]);
const activeUsers = store.computedList((users) =>
users.value.filter((u) => u.active),
);
const sortedUsers = store.sort((a, b) => a.name.localeCompare(b.name));
🏗️ Gerenciamento Avançado com useStores
O composable useStores
centraliza criação e cache de múltiplos stores. Ele exige que você informe as factories padrão (createItemStore
, createTableStore
) uma única vez.
1. Uso Dinâmico (on-demand)
import {
useStores,
createItemStore,
createTableStore,
} from "@ministerjs/store";
const { generateItem, generateTable, getCacheSize } = useStores(undefined, {
createItemStore,
createTableStore,
});
console.log(getCacheSize());
const userProfile = generateItem<UserProfile>("profile", {
debug: true,
initialState: { name: "", email: "" },
});
const usersTable = generateTable<User>("users", {
primaryKey: "id",
debug: true,
});
const profileAgain = generateItem("profile");
2. Stores Pré-configurados
import {
useStores,
createItemStore,
createTableStore,
} from "@ministerjs/store";
const stores = useStores(
{
users: () =>
createTableStore<User>({ primaryKey: "id", displayName: "Users" }),
profile: () =>
createItemStore<UserProfile>({ displayName: "Profile", debug: true }),
settings: () =>
createItemStore<AppSettings>({
displayName: "App Settings",
initialState: { theme: "light", language: "pt-BR" },
}),
},
{
createItemStore,
createTableStore,
},
);
const userStore = stores.get("users");
const userStoreAgain = stores.get("users");
console.log(stores.getCacheSize());
3. Integração Rápida em uma App Vue
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
const app = createApp(App);
app.use(createPinia());
app.mount("#app");
import {
useStores,
createItemStore,
createTableStore,
} from "@ministerjs/store";
export const stores = useStores(undefined, {
createItemStore,
createTableStore,
});
🔧 API Reference
ItemStore Interface
interface ItemStore<T> {
get(): Ref<Partial<T>>;
set(item: Partial<T>): void;
update(data: Partial<T>): void;
clear(): void;
}
TableStore Interface
interface TableStore<T, PK> {
add(data: T): void;
get(primaryKey: T[PK]): T | undefined;
list(): T[];
computedList<R>(callback: (items: Ref<T[]>) => R): Ref<R>;
getByProperty<K>(key: K, value: T[K]): T | undefined;
update(primaryKey: T[PK], data: Partial<T>): void;
remove(primaryKey: T[PK]): boolean;
replaceAll(data: T[]): void;
sort(callback: (a: T, b: T) => number): T[];
}
Opções de Configuração
interface CreateItemStoreOptions<T> {
displayName?: string;
initialState?: T;
debug?: boolean;
}
interface CreateTableStoreOptions<T, PK> {
primaryKey?: PK;
displayName?: string;
initialState?: T[];
debug?: boolean;
}
### useStores
```typescript
// Sem stores pré-configurados
useStores(undefined, {
createItemStore,
createTableStore,
});
// Com stores pré-configurados
useStores({
users: () => createTableStore<User>({ primaryKey: 'id' }),
profile: () => createItemStore<UserProfile>({}),
}, {
createItemStore,
createTableStore,
});
Métodos retornados:
generateItem(key, options?)
generateTable(key, options?)
get(key)
getCacheSize()
## 🏆 Boas Práticas
### 1. Organização de Stores
```typescript
// stores/user.ts
export const useUserProfile = createItemStore<UserProfile>({
displayName: "User Profile",
});
export const useUsers = createTableStore<User, "id">({
primaryKey: "id",
displayName: "Users",
});
// stores/index.ts
export const stores = useStores({
userProfile: () => useUserProfile(),
users: () => useUsers(),
});
2. Composables Especializados
export const useAuth = () => {
const profile = stores.get("userProfile");
const login = async (credentials: LoginData) => {
const user = await authService.login(credentials);
profile.set(user);
};
const logout = () => {
profile.clear();
};
return { login, logout, profile: profile.get() };
};
3. Integração com APIs
export const useUsersApi = () => {
const store = stores.get("users");
const fetchUsers = async () => {
const users = await api.get<User[]>("/users");
store.replaceAll(users);
};
const createUser = async (userData: Omit<User, "id">) => {
const user = await api.post<User>("/users", userData);
store.add(user);
return user;
};
return { fetchUsers, createUser, store };
};
🔗 Dependências & Peer Deps
Certifique-se de ter instalado (peer dependencies):
pnpm add vue pinia
Versões sugeridas:
- Vue >= 3.5
- Pinia >= 2.0
- TypeScript >= 5
O pacote não inicializa o Pinia automaticamente; você ainda precisa chamar app.use(createPinia())
em sua aplicação.
🧪 Testes
Dentro do monorepo:
pnpm test --filter @ministerjs/store
⚠️ Notas & Gotchas
primaryKey
em createTableStore
é opcional (default "id"
).
generateItem
e generateTable
não recebem a factory como argumento (a factory é injetada uma vez em useStores
).
computedList
recebe um callback que recebe um Ref<T[]>
e retorna um valor derivado reativo.
- Evite mutar diretamente objetos retornados por
get()
/ itens da lista fora de ações controladas se quiser padronizar logs.
- O cache vive no escopo onde
useStores
foi chamado; para um cache global, exporte a instância.
📄 Licença
UNLICENSED – Uso interno do MinisterJS Framework.
🤝 Contribuindo
Este pacote faz parte do monorepo MinisterJS. Para contribuir:
- Clone o repositório principal
- Instale as dependências:
pnpm install
- Execute os testes:
pnpm test
- Faça suas alterações no pacote
packages/store/
- Execute o build:
pnpm build
Para mais informações, consulte o README principal do MinisterJS.