A modern predictable state container built on the idea of channels, events, and payloads, giving you strongly typed, expressive actions
npm install @quojs/core> 🇲🇽 Versión en Español
> | 👉 🇵🇹 Versão Portuguesa
> | 🇺🇸 English Version
> | 🇫🇷 Version française
!Tamanho do bundle
!Tamanho do bundle
!Tamanho do bundle
!Tamanho do bundle
!Versão npm
!Downloads npm
!Licença
Contêiner de estado orientado a eventos, agnóstico de framework.
@quojs/core é a base do Quo.js—uma biblioteca moderna de gerenciamento de estado que combina
eventos baseados em canais, assinaturas atômicas e suporte nativo para async em um
pacote leve e universal.
Funciona em todos os lugares: Navegadores, Node.js 18+, Deno, Bun. Zero dependências do DOM.
---
@quojs/core fornece:
- Arquitetura orientada a eventos — Os eventos fluem através de canais
(channel, type, payload)
- Fila de eventos FIFO — Processamento de eventos previsível e serializado com garantias de
ordenação
- Async-first — Middleware e effects async nativos (sem thunks/sagas)
- Assinaturas de granularidade fina — Inscreva-se em caminhos de estado exatos via notação
de pontos
- Imutabilidade — Aplicação de deep-freeze com detecção de mudanças estruturais
- TypeScript-first — Excelente inferência de tipos e autocompletar
> Confira o relatório de
> Comparação de Bibliotecas de Gerenciamento de Estado.
---
``bash`
npm install @quojs/coreou
yarn add @quojs/coreou
pnpm add @quojs/core
---
---
Os eventos são cidadãos de primeira classe no Quo.js. Cada mudança de estado é acionada por um
evento explícito.
`typescript`
// Os eventos têm um canal, tipo e payload
await store.emit("auth", "login", { username, password });
await store.emit("analytics", "track", { event: "page_view" });
await store.emit("ui", "toast", { message: "Bem-vindo!" });
Benefícios:
- Intenção clara (cada ação é rastreável)
- Modularidade natural (organize por canal)
- Trilha de auditoria (eventos são serializáveis)
Confira o documento de visão geral da
Arquitetura de Fila de Eventos.
Middleware e effects são async por padrão. Nenhuma biblioteca externa necessária.
`typescript
// Middleware assíncrono
const authMiddleware = async (state, event, emit) => {
if (event.type === "login") {
const token = await authenticateUser(event.payload);
await emit("auth", "loginSuccess", { token });
return false; // Cancela o evento original
}
return true;
};
// Effects assíncronos (executam após os reducers)
const analyticsEffect = async (event, getState, emit) => {
if (event.channel === "analytics") {
await sendToAnalytics(event.payload);
}
};
const store = createStore({
name: "App",
reducer: {
/ ... /
},
middleware: [authMiddleware],
effects: [
{
events: [["analytics", "track"]],
effect: analyticsEffect,
},
],
});
`
Inscreva-se em caminhos de estado exatos usando notação de pontos:
`typescript
// Inscrever-se em caminho aninhado
store.connect({ reducer: "todos", property: "items.0.title" }, (change) => {
console.log("Título da primeira tarefa mudou:", change.newValue);
});
// Padrões wildcard
store.connect({ reducer: "todos", property: "items.*.completed" }, (change) => {
console.log("Status de conclusão de uma tarefa mudou");
});
`
O estado está deep-frozen antes de ser confirmado para prevenir mutações acidentais:
`typescript
const state = store.getState();
state.counter.value = 999; // ❌ TypeError: Não é possível atribuir a propriedade somente leitura
// Em vez disso, emita eventos:
await store.emit("counter", "set", 999); // ✅ Forma correta
`
---
#### 1. Sempre Aguarde emit()
`typescript
// ❌ RUIM: Disparar e esquecer
emit("todo", "add", todo);
const state = store.getState(); // Pode não ter a nova tarefa ainda!
// ✅ BOM: Aguardar a conclusão
await emit("todo", "add", todo);
const state = store.getState(); // Garantido que tem a nova tarefa
`
#### 2. Evite Loops Infinitos
`typescript
// ❌ RUIM: Recursão infinita
registerEffect({
events: [["counter", "increment"]],
effect: (evt, getState, emit) => {
emit("counter", "increment", evt.payload + 1); // Infinito!
},
});
// ✅ BOM: Condição de guarda
registerEffect({
events: [["counter", "increment"]],
effect: (evt, getState, emit) => {
if (evt.payload < 100) {
// Parar em 100
emit("counter", "increment", evt.payload + 1);
}
},
});
`
#### 3. Mantenha os Reducers Rápidos
`typescript
// ❌ RUIM: Reducer lento bloqueia a fila
reducer: (state, event) => {
const result = expensiveComputation(); // Bloqueia por segundos
return { ...state, result };
};
// ✅ BOM: Mover para effect assíncrono
reducer: (state, event) => {
return { ...state, loading: true };
};
registerEffect({
events: [["data", "compute"]],
effect: async (evt, getState, emit) => {
const result = await computeAsync(); // Não bloqueia
emit("data", "computeComplete", result);
},
});
`
#### 4. Trate Erros de Effects
`typescript
// ❌ RUIM: Erros de effect não tratados
effect: async (evt, getState, emit) => {
const data = await fetch(url); // Pode lançar erro
emit("data", "loaded", data);
};
// ✅ BOM: Tratamento de erros com eventos de falha
effect: async (evt, getState, emit) => {
try {
const data = await fetch(url);
emit("data", "loadSuccess", data);
} catch (error) {
emit("data", "loadFailure", { error: error.message });
}
};
`
#### 5. Limite Eventos de Alta Frequência
`typescript
// ❌ RUIM: Inunda a fila
window.addEventListener("mousemove", (e) => {
emit("ui", "mouseMove", { x: e.clientX, y: e.clientY });
});
// ✅ BOM: Limitar emissões
import { throttle } from "lodash-es";
const throttledEmit = throttle(
(x, y) => emit("ui", "mouseMove", { x, y }),
16, // ~60fps
);
window.addEventListener("mousemove", (e) => {
throttledEmit(e.clientX, e.clientY);
});
`
---
Adicione ou remova reducers em tempo de execução:
`typescript
// Adicionar um novo reducer dinamicamente
const disposeReducer = store.registerReducer("newFeature", {
state: { enabled: false },
events: [["features", "toggle"]],
reducer: (state, event) => {
return { enabled: !state.enabled };
},
});
// Mais tarde: remover o reducer
disposeReducer();
`
Quo.js previne automaticamente o processamento duplicado de eventos (seguro para React Strict
Mode):
`typescript`
// No React Strict Mode, effects disparam duas vezes em desenvolvimento
useEffect(() => {
emit("analytics", "pageView", { page });
// ↑ Disparado 2x pelo React, mas Quo.js processa apenas uma vez
}, [page]);
O middleware executa antes dos reducers e pode cancelar eventos:
`typescript
const loggingMiddleware = async (state, event, emit) => {
console.log("Evento:", event.channel, event.type, event.payload);
return true; // Permitir que o evento continue
};
const validationMiddleware = async (state, event) => {
if (event.type === "addTodo" && !event.payload.title) {
console.error("Tarefa inválida: falta o título");
return false; // Cancelar evento
}
return true;
};
`
Effects executam após os reducers e são ótimos para efeitos colaterais:
`typescript
const saveToLocalStorageEffect = async (event, getState) => {
const state = getState();
localStorage.setItem("app-state", JSON.stringify(state));
};
store.registerEffect({
events: [
["todos", "add"],
["todos", "toggle"],
["todos", "delete"],
],
effect: saveToLocalStorageEffect,
});
`
---
Quo.js é TypeScript-first com excelente inferência de tipos:
`typescript
// O mapa de eventos é totalmente tipado
type AppEM = {
counter: {
increment: number; // Tipo de payload
decrement: number;
};
};
const store = createStore
/ ... /
});
// ✅ O autocompletar funciona:
await store.emit("counter", "increment", 5);
// ↑ Sugere: 'increment' | 'decrement'
// ↑ Espera: number
// ❌ TypeScript detecta erros:
await store.emit("counter", "increment", "five"); // Erro: Esperado number
await store.emit("invalid", "event", null); // Erro: Canal desconhecido
`
---
- createStore(spec) — Criar uma nova instância de storestore.emit(channel, type, payload)
- — Emitir um evento (async)store.getState()
- — Obter estado atual (somente leitura)store.subscribe(listener)
- — Inscrever-se em qualquer mudança de estadostore.connect(spec, handler)
- — Inscrever-se em caminho de estado específico
- store.registerReducer(name, spec) — Adicionar reducer em tempo de execuçãostore.registerMiddleware(middleware)
- — Adicionar middleware em tempo de execuçãostore.registerEffect(spec)
- — Adicionar effect em tempo de execução
- store.replaceReducers(reducers, opts) — Substituir todos os reducers (HMR)store.replaceMiddleware(middleware)
- — Substituir todos os middleware (HMR)store.replaceEffects(effects)
- — Substituir todos os effects (HMR)
---
| Métrica | Valor |
| --------------------- | --------------------------------------- |
| Tamanho do Bundle | ~8KB (minificado + gzipped) |
| Tree-shakeable | ✅ Sim (módulos ES) |
| Dependências | Zero dependências em tempo de execução |
| TypeScript | Definições de tipos completas incluídas |
---
- Guia de Início Rápido — Comece em 5 minutos
- Referência da API TypeDoc — Documentação completa da API (English)
- Arquitetura de Fila de Eventos — Análise técnica
profunda
- Comparação de Bibliotecas — vs Redux, Zustand, Jotai,
etc.
---
- Aplicativo de Tarefas — Exemplo CRUD completo com
perfilamento de desempenho
- Logo Cinético — Simulação de física com 900
partículas
- Integração com Next.js — SSR + alternador de tema
---
| Antigo (v0.4.x) | Novo (v0.5.0+) | Status |
| --------------- | -------------- | -------------------------------------------- |
| dispatch() | emit() | ❌ Removido (use emit() no lugar) |Action
| | Event | ❌ Removido (use o tipo Event) |ActionMap
| | EventMap | ❌ Removido (use o tipo EventMapBase) |ActionPair
| | EventKey | ❌ Removido (use o tipo EventKey) |ActionUnion
| | EventUnion | ❌ Removido (use o tipo EventUnion) |Dispatch
| | Emit | ❌ Removido (use o tipo Emit) |typedActions
| | typedEvents | ❌ Removido (use a função typedEvents) |action.event
| | event.type | ⚠️ Mudança disruptiva |
`typescript
// ANTES (v0.4.x)
store.dispatch("counter", "increment", 1);
const actions = typedActions([])('counter', ['increment']);
type MyAction = Action;
// DEPOIS (v0.5.0+)
store.emit("counter", "increment", 1);
const events = typedEvents([])('counter', ['increment']);
type MyEvent = Event;
``
Nota: Todos os aliases depreciados foram removidos. Se você está atualizando da v0.4.x, deve atualizar seu código para usar a nova terminologia de event-bus.
---
Damos boas-vindas a contribuições! Veja:
- Raiz do Monorepo
- Guia de Contribuição
- Código de Conduta
---
Release Candidate — As APIs são estáveis, usadas em produção, mudanças menores possíveis
antes da v1.0.
---
MIT — Livre para usar em projetos comerciais e de código aberto.
---
Feito no 🇲🇽 para o mundo.