Manager
Managers são singletons que lidam com efeitos colaterais globais. Uma espécie de watchEffect() para o store
de dados central.
Os managers padrão orquestram o comportamento assíncrono complexo que o Data Client
fornece de fábrica. Eles podem ser facilmente configurados com getDefaultManagers() e
estendidos com seus próprios Managers personalizados.
Managers devem implementar middleware, que os conecta ao fluxo de controle do store central. Além disso, cleanup() e init() se conectam ao ciclo de vida do store para comportamentos de configuração/encerramento.
type Dispatch = (action: ActionTypes) => Promise<void>;
type Middleware = (controller: Controller) => (next: Dispatch) => Dispatch;
interface Manager {
middleware: Middleware;
cleanup(): void;
init?: (state: State<any>) => void;
}
Ciclo de vida
middleware
middleware é muito parecido com um middleware do redux.
A única diferença é que a função next() retorna uma Promise.
Essa promise resolve quando a atualização do reducer é confirmada no store do DataClientPlugin. Isso permite criar managers que executam trabalho com o estado recém-computado.
Como o redux é totalmente síncrono, é preciso colocar um adaptador na frente dos middlewares no estilo do Reactive Data Client para garantir que eles possam consumir uma promise. Inversamente, os middlewares do redux precisam ser alterados para repassar promises.
Middlewares interceptam actions que são despachadas e, em seguida, podem também despachar suas próprias actions. Para saber mais sobre middlewares, veja a documentação do redux.
init(state)
Chamado com o estado inicial depois que o provider é montado. Pode ser útil para executar, no início, uma configuração que depende de o estado realmente existir.
cleanup()
Realiza a limpeza de quaisquer recursos pendentes depois que o manager não está mais em uso.
Adicionando managers ao Reactive Data Client
Use a opção managers do DataClientPlugin. O plugin é instalado uma vez por app, então os managers são criados uma única vez.
import { createApp } from 'vue';
import { DataClientPlugin, getDefaultManagers } from '@data-client/vue';
import App from './App.vue';
import MyManager from './MyManager';
const managers = [...getDefaultManagers(), new MyManager()];
const app = createApp(App);
app.use(DataClientPlugin, { managers });
app.mount('#app');
Fluxo de controle
Managers se integram ao store do DataClientPlugin por meio de seus ciclos de vida e middleware. Eles orquestram fluxos de controle complexos interceptando e despachando actions, além de lerem o estado interno.

O trabalho do middleware é despachar actions, responder a actions, ou ambos.
Despachando actions
O Controller fornece dispatchers de actions com tipagem segura.
import type { Manager, Middleware } from '@data-client/vue'; import CurrentTime from './CurrentTime'; export default class TimeManager implements Manager { declare protected intervalID?: ReturnType<typeof setInterval>; middleware: Middleware = controller => { this.intervalID = setInterval(() => { controller.set(CurrentTime, { id: 1 }, { id: 1, time: Date.now() }); }, 1000); return next => async action => next(action); }; cleanup() { clearInterval(this.intervalID); } }
Lendo e consumindo actions
actionTypes inclui todas as constantes para distinguir entre diferentes actions.
import type { Manager, Middleware } from '@data-client/vue'; import { actionTypes } from '@data-client/vue'; export default class LoggingManager implements Manager { middleware: Middleware = controller => next => async action => { switch (action.type) { case actionTypes.SET_RESPONSE: if (action.endpoint.sideEffect) { console.info( `${action.endpoint.name} ${JSON.stringify(action.response)}`, ); // wait for state update to be committed await next(action); // get the data from the store, which may be merged with existing state const { data } = controller.getResponse( action.endpoint, ...action.args, controller.getState(), ); console.info(`${action.endpoint.name} ${JSON.stringify(data)}`); return; } // actions must be explicitly passed to next middleware default: return next(action); } }; cleanup() {} }
Em blocos condicionais, o tipo da action é restringido (narrowing), incentivando o acesso seguro aos seus membros.
Caso queiramos 'tratar' uma determinada action, podemos 'consumi-la' não chamando next.
import type { Manager, Middleware, EntityInterface, } from '@data-client/vue'; import { actionTypes } from '@data-client/vue'; import isEntity from './isEntity'; export default class CustomSubsManager implements Manager { declare protected entities: Record<string, EntityInterface>; middleware: Middleware = controller => next => async action => { switch (action.type) { case actionTypes.SUBSCRIBE: case actionTypes.UNSUBSCRIBE: const { schema } = action.endpoint; // only process registered entities if (schema && isEntity(schema) && schema.key in this.entities) { if (action.type === actionTypes.SUBSCRIBE) { this.subscribe(schema.key, action.args[0]?.product_id); } else { this.unsubscribe(schema.key, action.args[0]?.product_id); } // consume subscription if we use it return Promise.resolve(); } default: return next(action); } }; cleanup() {} subscribe(channel: string, product_id: string) {} unsubscribe(channel: string, product_id: string) {} }
Ao usar return Promise.resolve(); em vez de chamar next(action), impedimos que os managers listados
depois deste vejam essa action.
Tipos: FETCH, SET, SET_RESPONSE,
RESET, SUBSCRIBE, UNSUBSCRIBE,
INVALIDATE, INVALIDATEALL, EXPIREALL
Casos de uso
Exemplos mínimos para casos de uso comuns de Manager:
- Logging
- Relatório de erros (monitoramento)
- Métricas (tempo de fetch)
- Notificações (toasts)
- Atualizar ao focar ou reconectar
- Sincronização entre abas
- Persistência offline
- Streams de dados (websockets/SSE)
- Autenticação: logout em 401
- Atualizações periódicas (interval/ticker)
- Assinaturas de transporte personalizado