# Eventos, RPC y contratos

Tres mecanismos concretos de comunicación: notificar hechos, pedir respuestas y consumir servicios versionados.

<span class="section-anchor" id="section-1"></span>

## 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](/es/docs/guides/rpc-between-apps/): cliente, proveedor, manifests, grants y página Astro, con una respuesta observable y un caso de error recuperable.

<span class="section-anchor" id="section-2"></span>

## Eventos y envelope

```js
// 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.

<span class="section-anchor" id="section-3"></span>

## RPC con timeout y versión

```js
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.

<span class="section-anchor" id="section-4"></span>

## Contratos de métodos locales

```js
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.

<span class="section-anchor" id="section-5"></span>

## 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 |

<span class="section-anchor" id="section-6"></span>

## 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.