# Events, RPC, and contracts

Three concrete communication mechanisms: announce facts, request responses, and consume versioned services.

<span id="elige-por-el-tipo-de-interacción"></span>

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

## Choose by interaction type

| Need | API | Result |
| --- | --- | --- |
| Announce a fact without a response | `emit` / `on` | Event with identity and data |
| Request a response from a provider | `request` / `handle` | Promise with a value or error |
| Invoke methods on a local service | `provide` / `consume` | Proxy for a versioned contract |

All three mechanisms live in the browser's JavaScript process. RPC in this context does not imply HTTP or transport between servers. An implementation may call fetch internally, but that effect and its authorization belong to the implementation.

Snippets using `wu` assume `import { wu } from 'wu-framework'` in the shell's client module and initialization with `await wu.init(...)`. Snippets using `appWu` use the `context.wu` delivered to an app in `mount(container, context)`. For a complete copyable example, follow [Adding numbers between applications](/docs/guides/rpc-between-apps/): client, provider, manifests, grants, and Astro page, with an observable response and a recoverable error case.

<span id="eventos-y-envelope"></span>

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

## Events and envelopes

```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();
```

The callback receives an object with `name`, `data`, `timestamp`, `appName`, `meta`, and `verified`. Data is detached and frozen when crossing the bus boundary. `once` removes its subscription after the first delivery; `off` removes a specific subscription.

From an app, use `context.wu.on` and `context.wu.emit`. The facade attaches its identity and checks grants. In strict mode, emitting without a valid identity, without a capability, or to a reserved namespace returns `false`. Denied subscriptions and RPC may throw errors: check the boolean from `emit` and handle exceptions from APIs that produce them.

The bus stores up to 100 events by default. `eventBus.replay(pattern, callback)` delivers existing entries again to the supplied callback; it does not rerun the system or guarantee recovery of external effects. `history: false` excludes an emission from history.

<span id="rpc-con-timeout-y-versión"></span>

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

## RPC with timeout and version

```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();
```

A provider app needs `rpc.provide` and a consumer needs `rpc.call` for the channel. Additionally, `allow` filters caller identities. For an app-scoped provider in strict mode, the default allow permits that same app; explicitly declare consumers for communication between apps.

`wu.handle(channel, handler, options)` preserves `version`, `allow` and `replace`. Inside the lifecycle, `context.wu.handle` also adds the app’s identity and grants; use that facade for microapp handlers.

The default `request` timeout is **0**, with no time limit. Use a finite timeout for UI interactions. The handler receives a second argument with correlation and identity data and a `signal`. It must cooperate with that signal to cancel its own work; rejecting the wait does not undo an operation already performed.

A channel has one active provider. Replacing the same provider requires `{ replace: true }` under strict conditions; another identity cannot take over an already owned channel. Releasing, replacing, or revoking the provider aborts associated requests. The bus emits `<canal>:request`, `<canal>:response`, and `<canal>:error` traces with correlation.

<span id="contratos-de-métodos-locales"></span>

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

## Local method contracts

```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` returns a live proxy. Without `{ wait: true }`, creating the proxy does not require a provider to exist: the first member access validates existence and version. With `{ wait: true, timeout: 2500 }`, it waits until a compatible provider is found.

The default `provide` version is `0.0.0`, unlike RPC `handle`, whose default is `1.0.0`. `shape` checks member presence or `typeof`; it does not replace a complete argument/result schema.

An app facade also requires `rpc.provide`/`rpc.call` for contracts and automatically associates the provider with the app. Consumed proxies are not promises; a method literally named `then` cannot be reached through them. Contracts do not implement the same allowlist or payload separation as bus RPC; choose a contract with that limit in mind.

<span id="errores-que-conviene-manejar"></span>

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

## Errors to handle

| RPC code | Cause |
| --- | --- |
| `WU_RPC_NO_HANDLER` | No channel provider exists |
| `WU_RPC_VERSION_MISMATCH` | The version does not satisfy the range |
| `WU_RPC_CALLER_DENIED` | The consumer is not in `allow` |
| `WU_RPC_CALL_DENIED` / `WU_RPC_PROVIDE_DENIED` | An effective grant is missing |
| `WU_RPC_CHANNEL_OWNED` | Another owner has already registered the channel |
| `WU_RPC_REPLACE_REQUIRED` | Explicit replacement intent is missing |
| `WU_RPC_PROVIDER_RELEASED` / `WU_RPC_PROVIDER_REPLACED` | The provider changed during the request |

<span id="referencia-de-implementación"></span>

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

## Implementation references

Events: `src/core/wu-event-bus.js`; RPC: `src/core/wu-event-bus.js` and `src/core/wu-event-bus.js`; singleton handle shortcut: `src/index.js`; replay: `src/core/wu-event-bus.js`; contracts: `src/core/wu-contracts.js` and `src/core/wu-contracts.js`; facade: `src/core/wu-core.js`. Tests: `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`.

The first provider owns a contract name. Another app, or the host replacing an app’s contract, receives `WU_CAPABILITY_OWNED` until the owner revokes the contract or unmounts. The same owner can replace its implementation. When a `shape` exists, remote ports expose only its declared methods.