WU / DOCSUNIVERSAL MICROFRONTENDS
EN

Events, RPC, and contracts

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

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: client, provider, manifests, grants, and Astro page, with an observable response and a recoverable error case.

Events and envelopes

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

RPC with timeout and version

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.

Local method contracts

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.

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

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.

BUILD WU WITH US

Found a bug? Have an idea?

Your experience improves WU. Tell us what went wrong or help support its development.