Estado compartilhado
Leituras separadas, escritas copy-on-write, inscrições por caminho, batch e sincronização entre réplicas.
Um dado, vários renderizadores
O store permite que React, Vue e o shell projetem o mesmo estado. Seus dados são endereçados por caminhos separados por pontos. O dado compartilhado deve descrever o domínio ou a interação; nós DOM, raízes React e instâncias Vue pertencem ao ciclo de vida de cada app.
Para implementar isso em um workspace real, siga Um contador entre React e Vue. Você encontrará os componentes completos, leitura inicial, inscrição, limpeza e os dois lados das permissões: manifesto e shell.
Os próximos fragmentos ficam no módulo cliente do shell, onde wu é importado de wu-framework. Dentro do ciclo de vida de uma microapp, use context.wu e solicite as permissões store.read e store.write para os caminhos necessários; o shell deve concedê-las. Configuração e permissões mostra as duas partes. Para observar uma escrita compartilhada, abra React e Vue e clique em +1 a partir de React.
wu.store.set('catalog.selection', { id: 'p-42', quantity: 1 });
const stop = wu.store.on('catalog.selection', (selection) => {
console.log(selection.id, selection.quantity);
});
wu.store.update('catalog.selection', (selection) => ({
...selection,
quantity: selection.quantity + 1,
}));
await Promise.resolve(); // Permite entregar las notificaciones de la microtask.
stop();
set e update retornam números de sequência. update entrega um snapshot separado ao updater e exige um resultado síncrono; uma promise é rejeitada. Para ler dados assíncronos, faça fetch fora e depois escreva o resultado.
Cópias, identidade e reatividade
get(path) e getSnapshot(path) retornam dados separados do estado interno. Modificar o objeto recebido não altera o store. As escritas fazem copy-on-write e o estado interno é congelado, de modo que uma escrita descendente não modifica retroativamente entradas anteriores do histórico.
const selection = wu.store.get('catalog.selection');
selection.quantity = 99;
console.log(wu.store.get('catalog.selection').quantity); // Sigue en el store
Cada leitura de um objeto produz um novo snapshot. Não conecte store.get() diretamente como um getSnapshot do React useSyncExternalStore sem manter a identidade entre notificações. Para uma ponte manual, guarde um snapshot local e substitua-o apenas quando chegar uma notificação; você também pode usar o estado do framework e atualizá-lo por on.
As notificações são agendadas em microtasks. Imediatamente após set, uma leitura vê o novo estado, mas o callback não necessariamente foi executado. As inscrições não entregam um valor inicial automaticamente: leia uma vez ao conectar.
Inscrição exata e wildcard
| Inscrição | O que observa | Callback |
|---|---|---|
on('catalog', fn) |
Escrita em catalog e em caminhos descendentes | (valorDeCatalog, 'catalog') |
on('catalog.selection', fn) |
Esse caminho e seus descendentes | (valorDelPath, pathSuscrito) |
on('catalog.*', fn) |
Um segmento imediatamente abaixo de catalog | ({ path, value }) |
on('*', fn) |
Todas as escritas | ({ path, value }) |
catalog.* não corresponde a catalog nem a catalog.selection.quantity: o wildcard substitui um segmento, não a subárvore inteira. Ao substituir um pai, todas as inscrições exatas dos filhos não são notificadas automaticamente como se tivessem sido escritas separadamente. Para um componente que projeta um objeto completo, inscreva-o no caminho do objeto e leia-o novamente.
Selecione o payload conforme a inscrição, mesmo quando uma assinatura genérica TypeScript não distinguir os dois casos:
const stopValue = wu.store.on('catalog.selection', (selection) => {
console.log('Selección actual', selection);
});
const stopChanges = wu.store.on('catalog.*', ({ path, value }) => {
console.log('Campo escrito', path, value);
});
wu.store.set('catalog.selection', { id: 'p-42', quantity: 2 });
await Promise.resolve();
// Al retirar el consumidor, libera ambas suscripciones.
stopValue();
stopChanges();
Valores aceitos
O codec local aceita null, booleanos, strings, números finitos, bigint escalar, arrays densos e objetos simples de dados. Rejeita funções, símbolos, undefined como valor escrito, ciclos, accessors, arrays esparsos, instâncias como Date/Map e estruturas que excedem 256 níveis ou 100 000 nós.
Uma leitura de um caminho inexistente pode retornar undefined. Para datas compartilhadas, use strings ISO; para IDs grandes que sairão por JSON, use strings. Bigint local não implica compatibilidade com sync, cujas mensagens exigem dados JSON seguros.
Os caminhos rejeitam segmentos vazios e escritas em __proto__, prototype ou constructor. Uma leitura perigosa retorna undefined sem expor objetos internos.
Batch e limites de atomicidade
wu.store.batch({
'catalog.selection': { id: 'p-42', quantity: 2 },
'checkout.step': 'review',
});
batch valida e separa todos os caminhos/valores antes de realizar uma escrita. Se essa verificação prévia falhar, não altera estado, sequência ou métricas. Após a validação, cada entrada mantém sua própria escrita e sequência; os taps de baixo nível observam commits individuais. Não é uma transação de banco de dados nem uma transação sobre efeitos externos.
Sincronização opcional
const sync = wu.store.sync({ transport: 'broadcast', room: 'docs-demo' });
await sync.ready();
// Escribir después de conectar registra estos paths para otros participantes.
wu.store.set('demo.counter', 1);
console.log(sync.status());
// Al abandonar la sesión:
sync.stop();
O módulo é carregado de forma adiada. BroadcastChannel permite comunicação entre contextos compatíveis do navegador; WebSocket e transportes personalizados ampliam o transporte. A resolução de conflitos usa Last Writer Wins por caminho, ordenado por relógio Lamport e identificador do site.
Só são rastreadas escritas feitas após iniciar sync. Paths pai/filho compartilham a ordem Lamport: um pai mais novo oculta filhos anteriores, e um filho mais novo sobrevive a um pai tardio. Snapshots de entrada preservam essa ordem. Substituições da raiz não são sincronizadas, e a persistência não é automática.
O transporte WebSocket incorporado exige authorizePeer(message) síncrono, a menos que o host declare trustTransport: true porque um relay já autenticou os participantes. Compartilhar um nome de sala não autoriza dados.
Referência de implementação
Codec: src/core/wu-store.js; leitura/escrita: src/core/wu-store.js; atualização: src/core/wu-store.js; batch: src/core/wu-store.js; notificações: src/core/wu-store.js; sync: src/core/wu-store-sync.js. Testes: tests/unit/store.test.js, tests/unit/store-snapshot-identity.test.js, tests/unit/store-subscription-semantics.test.js, tests/unit/store-sync.test.js.
Escopo das réplicas
paths limita operações de entrada e saída por prefixos de segmentos: cart inclui esse path e seus descendentes. authorizeOp(op, message) deve retornar true de forma síncrona para aceitar uma operação de entrada. Escritas remotas vão diretamente ao store; grants de apps não as autorizam. O runtime também limita saltos do relógio Lamport.
const sync = wu.store.sync({
transport: 'broadcast', room: 'cart-demo', paths: ['cart'],
authorizeOp: op => op.path === 'cart.count' && Number.isSafeInteger(op.value),
});
await sync.ready();
wu.store.set('cart.count', 1);
// Teardown:
sync.stop();