Eventos, RPC y contratos
Tres mecanismos concretos de comunicación: notificar hechos, pedir respuestas y consumir servicios versionados.
Elige por el tipo de interacción
| Necesidad | API | Resultado |
|---|---|---|
| Notificar un hecho sin respuesta | emit / on |
Evento con identidad y datos |
| Pedir una respuesta a un proveedor | request / handle |
Promesa con valor o error |
| Invocar métodos de un servicio local | provide / consume |
Proxy de un contrato versionado |
Los tres mecanismos viven en el proceso JavaScript del navegador. RPC en este contexto no implica HTTP ni transporte entre servidores. Una implementación puede hacer fetch por dentro, pero ese efecto y su autorización pertenecen a la implementación.
Los fragmentos que usan wu presuponen import { wu } from 'wu-framework' en el módulo cliente del shell y su inicialización con await wu.init(...). Los fragmentos con appWu usan el context.wu entregado a una app en mount(container, context). Para copiar un ejemplo completo, sigue Una suma entre aplicaciones: cliente, proveedor, manifests, grants y página Astro, con una respuesta observable y un caso de error recuperable.
Eventos y envelope
// Código del shell, con autoridad host.
const stop = wu.on('catalog:*', (event) => {
console.log(event.name, event.data, event.appName, event.verified);
});
const accepted = wu.emit('catalog:selected', { productId: 'p-42' });
if (!accepted) console.info('El bus rechazó el evento');
stop();
El callback recibe un objeto con name, data, timestamp, appName, meta y verified. Los datos se separan y congelan al cruzar la frontera del bus. once elimina su suscripción después de la primera entrega; off retira una suscripción concreta.
Desde una app usa context.wu.on y context.wu.emit. La facade adjunta su identidad y verifica los grants. En modo estricto, emitir sin identidad válida, sin capacidad o hacia un namespace reservado devuelve false. Las suscripciones y el RPC denegados pueden lanzar errores: comprueba el booleano de emit y maneja las excepciones de las APIs que las producen.
El bus guarda hasta 100 eventos por defecto. eventBus.replay(pattern, callback) vuelve a entregar entradas existentes al callback suministrado; no vuelve a ejecutar el sistema ni garantiza recuperar efectos externos. history: false permite excluir una emisión del historial.
RPC con timeout y versión
const release = wu.eventBus.handle('catalog:lookup', ({ productId }) => {
return { productId, title: 'Producto de demostración' };
}, { version: '1.0.0', allow: ['search'] });
// Dentro del lifecycle de la app search, con rpc.call:catalog:lookup:
async function lookup(appWu, productId, signal) {
return appWu.request('catalog:lookup', { productId }, {
version: '^1.0.0',
timeout: 2500,
signal,
});
}
// Cuando el proveedor del shell ya no deba atender:
release();
Una app proveedora necesita rpc.provide y una consumidora rpc.call sobre el canal. Además, allow filtra identidades de llamadas. Para un proveedor app scoped en modo estricto, el allow por defecto permite a esa misma app; para comunicación entre apps declara los consumidores explícitos.
wu.handle(channel, handler, options) conserva version, allow y replace. Dentro del lifecycle, context.wu.handle añade además la identidad y los grants de la app; utiliza esa facade para handlers de una microapp.
El timeout por defecto de request es 0, sin límite temporal. Usa uno finito para interacciones de UI. El handler recibe un segundo argumento con datos de correlación e identidad y un signal. Debe cooperar con ese signal para cancelar trabajo propio; rechazar la espera no revierte una operación ya realizada.
Un canal tiene un proveedor activo. Reemplazar el mismo proveedor requiere { replace: true } bajo las condiciones estrictas; otra identidad no puede apropiarse de un canal ya poseído. Liberación, reemplazo y revocación del proveedor abortan solicitudes asociadas. El bus emite trazas <canal>:request, <canal>:response y <canal>:error con correlación.
Contratos de métodos locales
const handle = wu.provide('formatters', {
price(value) {
return new Intl.NumberFormat('es-MX', {
style: 'currency', currency: 'MXN',
}).format(value);
},
}, {
version: '1.0.0',
shape: { price: 'function' },
});
const formatters = wu.consume('formatters', '^1.0.0');
console.log(formatters.price(120));
handle.revoke();
consume entrega un proxy vivo. Sin { wait: true }, crear el proxy no exige que exista el proveedor: el primer acceso a un miembro valida existencia y versión. Con { wait: true, timeout: 2500 }, espera hasta encontrar un proveedor compatible.
La versión por defecto de provide es 0.0.0, distinta de handle RPC, cuyo default es 1.0.0. shape verifica presencia o typeof de miembros; no sustituye un schema completo de argumentos/resultados.
Una facade app exige rpc.provide/rpc.call también para contratos y asocia automáticamente el proveedor a la app. Los proxies consumidos no son promesas; un método llamado literalmente then no se alcanza por esa vía. Contratos no implementa la misma allowlist ni la misma separación de payloads que el RPC del bus; elige el contrato atendiendo a ese límite.
Errores que conviene manejar
| Código RPC | Causa |
|---|---|
WU_RPC_NO_HANDLER |
No hay proveedor del canal |
WU_RPC_VERSION_MISMATCH |
La versión no satisface el rango |
WU_RPC_CALLER_DENIED |
El consumidor no está en allow |
WU_RPC_CALL_DENIED / WU_RPC_PROVIDE_DENIED |
Falta grant efectivo |
WU_RPC_CHANNEL_OWNED |
Otro dueño ya registró el canal |
WU_RPC_REPLACE_REQUIRED |
Falta intención explícita de reemplazo |
WU_RPC_PROVIDER_RELEASED / WU_RPC_PROVIDER_REPLACED |
El proveedor dejó de ser el mismo durante la petición |
Referencia de implementación
Eventos: src/core/wu-event-bus.js; RPC: src/core/wu-event-bus.js y src/core/wu-event-bus.js; atajo handle del singleton: src/index.js; replay: src/core/wu-event-bus.js; contratos: src/core/wu-contracts.js y src/core/wu-contracts.js; facade: src/core/wu-core.js. Pruebas: tests/unit/event-bus.test.js, tests/unit/contracts.test.js, tests/unit/capability-security-v3.test.js, tests/unit/scoped-events-teardown.test.js.
El primer proveedor conserva la propiedad del nombre de un contrato. Otra app, o el host sobre un contrato de app, recibe WU_CAPABILITY_OWNED hasta que el propietario revoque el contrato o se desmonte. El mismo propietario puede reemplazar su implementación. Cuando hay shape, los puertos remotos solo exponen los métodos declarados por ella.