# A service connected through WU

Publish a contract, authorize a client, and follow RPC requests through versioning, validation, cancellation, and cleanup.

<span id="del-cliente-a-una-respuesta"></span>

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

## From client to response

If this is your first RPC integration, start with [Adding numbers between applications](/docs/guides/rpc-between-apps/). That recipe includes every file for your workspace. This chapter extends the pattern with contract discovery, cancellation, and the laboratory's runnable service.

In [Services and RPC](/labs/backend/), two WU applications collaborate with different permissions. The provider publishes the service it offers and handles a channel; the client consumes that contract and requests a response. The walkthrough lets you test valid data, errors, version incompatibility, cancellation, and provider removal.

```text
Cliente WU
  → consume('docs:backend:service', '^1.0.0')
  → request('docs:backend:request', solicitud)
  → handler del proveedor WU
  → respuesta { status, headers, body, execution }
```

Requests travel through WU RPC within the runtime. `status`, `headers`, and `body` are members of the response contract; they do not mean the portal has opened an HTTP connection or a server port.

<span id="solicitar-y-conceder-la-autoridad-necesaria"></span>

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

## Request and grant the required authority

Each manifest declares the capabilities its app requests. The provider needs `rpc.provide:docs:backend:*`; the client needs `rpc.call:docs:backend:*`. The shell grants those scopes when initializing both applications:

```js
await wu.init({
  sandbox: 'module',
  apps: [
    {
      name: 'backend-service',
      url: '/microfrontends/backend-service',
      grants: ['rpc.provide:docs:backend:*'],
    },
    {
      name: 'backend-client',
      url: '/microfrontends/backend-client',
      grants: ['rpc.call:docs:backend:*'],
    },
  ],
});
```

Manifest requests and shell grants must match. Effective permission comes from their intersection. App code uses `context.wu`, the scoped facade received in `mount`; it does not need to capture the host's privileged singleton. The [permissions reference](/docs/framework/security/) explains scopes and revocation.

<span id="publicar-el-canal-y-el-contrato"></span>

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

## Publish the channel and contract

Within the provider's mount, `appWu` is that scoped facade and `handleRequest` implements the service:

```js
const release = appWu.handle(
  'docs:backend:request',
  handleRequest,
  { version: '1.0.0', allow: ['backend-client'] },
);

const contract = appWu.provide(
  'docs:backend:service',
  { channel: 'docs:backend:request', method: 'POST' },
  {
    version: '1.0.0',
    shape: { channel: 'string', method: 'string' },
  },
);
```

`handle` registers who responds on the channel. `provide` publishes the data the consumer needs to discover it. The handler's allowlist limits callers to `backend-client`; having a grant does not remove this additional check. `shape` checks contract members and their types, while body validation belongs to the service.

In WU Framework 4.0.0, `wu.handle(channel, handler, options)` and `wu.eventBus.handle(channel, handler, options)` preserve version and allowlist. Inside a microapp use `context.wu.handle`: in addition to those options, it applies the app identity and grants. See the [versions page](/sources/) to identify the laboratory runtime.

The name published with `provide` belongs to the first provider. Another app, or the host attempting to replace it, receives `WU_CAPABILITY_OWNED` until the owner calls `revoke` or is unmounted with `unmount`. The contract version does not bypass that ownership.

<span id="consumir-y-llamar-desde-el-cliente"></span>

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

## Consume and call from the client

The client obtains a contract proxy and requests a compatible range. Accessing `api.channel` resolves the available provider and checks that range:

```js
const api = appWu.consume('docs:backend:service', '^1.0.0');
const channel = api.channel;
const controller = new AbortController();

const pending = appWu.request(channel, {
  method: api.method,
  path: '/validate',
  body: JSON.stringify({ name: 'catalog', entry: 'entry.js' }),
}, {
  version: '^1.0.0',
  timeout: 3500,
  signal: controller.signal,
});

const response = await pending;
console.log(response.status, response.body);
```

There are two compatibility checks: one for the consumed contract and one for the RPC handler. The incompatible-version scenario requests `^2.0.0` from the `1.0.0` contract; resolution fails before any service work starts. A removed provider cannot handle a new request either. See [events, RPC, and contracts](/docs/framework/communication/) for their signatures and errors.

<span id="diferenciar-una-respuesta-de-un-rechazo"></span>

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

## Distinguish a response from a rejection

The service expects `POST`, a known route, and a string body of at most 4096 UTF-8 bytes.

| Request | Service response |
| --- | --- |
| `/validate` with valid JSON and both `name`/`entry` present | 200 with `ok: true` |
| `/validate` with a manifest that does not meet those rules | 422 with validation errors |
| `/uppercase` | 200 with the ASCII text converted to uppercase |
| `/failure` | 500 for the service's controlled failure |
| `/slow` without cancellation | 504 when the 2500 ms work limit expires |
| Unknown route | 404 without starting work |
| Method other than POST | 405 |
| Invalid request structure | 400 |
| Body exceeds the limit | 413 |

These responses resolve the RPC promise with an object: the client must check `response.status`. Contract incompatibility, a missing provider, or cancellation are handled as rejections using `try`/`catch`.

The manifest recipe validates only `name` and `entry`. Its result declares `scope: "name+entry"` and `completeValidation: false`; it does not check the entire manifest, permissions, published routes, or whether the entry exists. See [configuration and manifests](/docs/framework/configuration/) and [WU doctor](/docs/cli/doctor/) to continue diagnosing a project.

<span id="cancelar-y-desmontar-sin-conservar-trabajo-activo"></span>

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

## Cancel and unmount without leaving work active

While the request remains pending, the client can run:

```js
controller.abort();
```

The handler receives the RPC context, which includes `signal`. In this example, the provider connects that signal to interruption of its executor. A handler that ignores `signal` can keep working after the client stops waiting.

The 3500 ms RPC timeout and the 2500 ms work limit have different effects: the first limits how long WU waits; the second lets the service stop work and return 504. The interface includes a slow recipe to observe cancellation before either deadline.

When the provider unmounts, the implementation stops its active requests and removes what it published:

```js
backend.dispose();
release();
contract.revoke();
```

WU also revokes the authority associated with the app. Mounting again registers a new handler and contract. Each request releases its abort listener, timer, and executor on completion, failure, or cancellation. The [lifecycle guide](/docs/framework/lifecycle/) explains how to connect that resource ownership to `mount` and `unmount`.

<span id="leer-y-comprobar-el-ejemplo"></span>

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

## Read and verify the example

The [provider code](/microfrontends/backend-service/service.js.txt) shows request handling and cleanup. To run the checks from the portal repository:

```sh
npm ci
npm run build
npm run dev:shell
# En otra terminal:
npm run test:backend
```

The test covers responses, incompatible versions, revocation, remounting, timeout, cancellation, and unmounting during a request. It also verifies that no Workers remain after execution and that the walkthrough produces no page errors.

<span id="implementación-del-ejemplo"></span>

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

## Example implementation

The provider uses a compiled WASI program included in the project, running in a Worker. This lets it process real data and stop work from the browser. The host and virtual files belong to the service implementation; the public contract studied here is WU. The framework does not include a general-purpose WASI engine.

You can replace that implementation with an authorized service from your application while keeping a compatible contract and testing its responses, errors, and cleanup. A remote service must validate identity, permissions, and data again. Rendering pages in Node or connecting an external MCP client still requires their processes and transports: see [SSR](/docs/framework/server-recipes/) and [AI/MCP](/docs/framework/ai-recipes/).