Skip to content
2 changes: 2 additions & 0 deletions component-model/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
- [Interfaces](./design/interfaces.md)
- [Worlds](./design/worlds.md)
- [Packages](./design/packages.md)
- [Async, Streams, and Futures](./design/async.md)
- [WIT By Example](./design/wit-example.md)
- [WIT Reference](./design/wit.md)
- [Migrating from WASI 0.2 to WASI 0.3](./design/migrating-to-p3.md)

# Using WebAssembly Components

Expand Down
65 changes: 65 additions & 0 deletions component-model/src/design/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Async, Streams, and Futures

WASI 0.3 is built on three new Canonical ABI primitives in the Component Model: `async func`, `stream<T>`, and `future<T>`. Together, they let interfaces express asynchronous operations that compose across component boundaries.

For migration mechanics (e.g., how a WASI 0.2 component maps onto these primitives) see [Migrating from WASI 0.2 to WASI 0.3](./migrating-to-p3.md). For the WASI release view, including the full per-interface diff, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev. This page focuses on the Component Model concepts themselves.

## Native async

The Component Model's Canonical ABI defines how typed values cross component boundaries. Until WASI 0.3, that vocabulary had no notion of suspension or asynchronous completion; every interface call returned synchronously, and asynchronous I/O was modeled with resources (`pollable` for readiness, `input-stream` and `output-stream` for byte channels) scoped to whichever component obtained them.

That arrangement holds up for two-party interactions, but it falters once components are composed in a chain. If a component awaits work that another component delegates further, the readiness signal has to travel back up the chain. When readiness is expressed as a resource scoped to a single component, the intermediate component is stuck running an event loop purely to forward the wake-up to its caller; the runtime cannot help, because the resource doesn't live in a place the runtime can reach across. This is sometimes called the **sandwich problem**: an async vocabulary that describes a single hop just fine but cannot propagate readiness past one.

Native primitives close the gap. With `async func`, `stream<T>`, and `future<T>` in the Canonical ABI, scheduling and wake-up propagation become the runtime's job rather than any individual component's. Components can pass futures and streams along the chain without keeping their own event loops running to relay readiness.

## The three primitives

### `async func`

A WIT function declared `async` tells the runtime that the call may suspend before producing its result. The Canonical ABI handles the suspension and resumption; the guest doesn't see a `pollable`, and the host doesn't see a polling loop.

```wit
handle: async func(request: request) -> result<response, error-code>;
```

Code generated from the WIT picks up each language's natural async idiom: `async fn` in Rust, a `Promise`-returning function in JavaScript, a coroutine in Python.

### `stream<T>`

A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream<T>` is a Canonical ABI *value*, not a resource: it can be returned from a call, accepted as a parameter, and handed from one component to another without giving up ownership of the underlying buffer. The same value can also be passed straight through a middle component without that component having to relay any wake-ups.

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

### `future<T>`

A typed handle for a single value that will become available later. Like `stream<T>`, `future<T>` is a value rather than a resource, so it crosses component boundaries the same way a primitive does. A function returning `future<T>` does not block; the caller awaits the result when it needs it.

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## How the primitives work in WASI 0.3

### Stream plus terminal future

Reads return both a data channel and a completion handle, packed into a tuple:

```wit
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

The two halves are independent. The caller can consume the stream eagerly, sample it, or drop it part-way through; either way the future resolves once the operation has terminated, carrying the success-or-failure outcome. The same shape appears in stdin, filesystem reads, TCP receives, and directory listings.

### Stream parameter, future return

Writes use the symmetric shape: the guest supplies the data as a `stream<u8>` parameter, and the host returns a `future` that resolves once it has consumed the stream. Stdout, stderr, filesystem writes, and TCP sends all follow this shape:

```wit
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

## Where to go next

For an end-to-end Rust example that uses these primitives in practice, see [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md). For runtime support and CLI flags, see [Wasmtime](../running-components/wasmtime.md). For the WIT syntax in detail, see [WIT Reference](./wit.md).
4 changes: 4 additions & 0 deletions component-model/src/design/component-model-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ For example, the [wasi-http](https://github.com/WebAssembly/WASI/blob/main/propo
an `imports` world encapsulating the interfaces that an HTTP proxy depends on,
and a `proxy` world that depends on `imports`.

### Async, Streams, and Futures

The Component Model includes [`async func`, `stream<T>`, and `future<T>`](./async.md) as native Canonical ABI primitives, introduced alongside WASI 0.3. Together, they let interfaces express asynchronous operations that compose across component boundaries.

### Platforms

In the context of WebAssembly, a _host_ refers to a WebAssembly runtime
Expand Down
107 changes: 107 additions & 0 deletions component-model/src/design/migrating-to-p3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Migrating from WASI 0.2 to WASI 0.3

WASI 0.3 reshapes WASI's interfaces around the [native async primitives](./async.md) `async func`, `stream<T>`, and `future<T>`. Most of the changes in `wasi:cli`, `wasi:http`, `wasi:filesystem`, and `wasi:sockets` are consequences of moving to these primitives.

This page covers the mapping between concepts in WASI 0.2 and WASI 0.3. For a WIT-level comparison of every WASI 0.3 interface, see [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev.

## Do you need to migrate?

Not immediately. WASI 0.3 runtimes can polyfill 0.2 by mapping 0.2 imports onto native 0.3 primitives at the host boundary, and Wasmtime's `wasmtime serve` already runs both 0.3 and 0.2 components from the same binary, dispatching per component. Migration is the right call when you want:

- Composable async across component boundaries (the [sandwich problem](./async.md#native-async) goes away).
- The newer interface shapes — in particular, `wasi:http`'s collapse of nine resources down to two.
- First-class support in 0.3-targeted toolchains as they continue to land.

## Concept mapping

WASI 0.3 replaces every `wasi:io` resource with a Canonical ABI primitive. The translation is mostly one-to-one:

| WASI 0.2 (`wasi:io`) | WASI 0.3 (Component Model) |
| -------------------------------- | ---------------------------------------- |
| `resource pollable` | `future<T>` |
| `resource input-stream` | `stream<u8>` |
| `resource output-stream` | `stream<u8>` (passed *into* the call) |
| `poll(list<pollable>)` | `await` on a future |
| `subscribe()` on a resource | return a `future` from the call |
| `start-foo` / `finish-foo` | a single `func` or `async func` |

## What changed in WIT

### Stream-plus-future for reads

A WASI 0.2 read call returned a single `input-stream` resource and surfaced terminal errors only as you consumed it. WASI 0.3 splits those concerns: the call returns a `stream<u8>` for the data and a `future<result<_, error-code>>` for the outcome, packed into a tuple.

```wit
// WASI 0.2 (filesystem read)
read-via-stream: func(offset: filesize) -> result<input-stream, error-code>;

// WASI 0.3 (filesystem read)
read-via-stream: func(offset: filesize) -> tuple<stream<u8>, future<result<_, error-code>>>;
```

In WASI 0.3, the caller does not have to drain the stream to learn whether the read finished cleanly; the future resolves either way.

### Write-direction flip

WASI 0.2 write paths handed a guest some host-owned resource (an `output-stream`) and let the guest push bytes into it. WASI 0.3 inverts that: the guest supplies the data as a `stream<u8>` value, and the host returns a `future` that resolves once it has finished consuming the stream.

```wit
// WASI 0.2: receive an output-stream resource, write into it
get-stdout: func() -> output-stream;

// WASI 0.3: pass a stream value in, receive a completion future
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

### Two-step calls collapsed

WASI 0.2 modeled operations that could suspend as a `start-foo` / `finish-foo` pair, with a `pollable` for readiness in between. WASI 0.3 collapses each pair into a single call:

```wit
// WASI 0.2
start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;

// WASI 0.3
connect: async func(remote-address: ip-socket-address) -> result<_, error-code>;
```

The collapsed call is `async func` when the operation needs to suspend in the host (such as `connect`); operations that historically only used the two-step shape for non-blocking dispatch may collapse to plain `func` instead (`bind`, `listen`).

## Interface highlights

The complete per-interface diff lives on [WASI 0.3](https://wasi.dev/releases/wasi-p3#what-changed-in-each-interface) at WASI.dev. The three changes most likely to drive migration work are:

- **`wasi:io` is gone.** The package has no 0.3.0 release. Every resource it exposed (`pollable`, `input-stream`, `output-stream`) is replaced by a Component Model primitive, per the [concept mapping](#concept-mapping) above.
- **`wasi:http` collapses from nine resources to two.** The incoming/outgoing × request/response/body matrix plus `future-trailers`, `future-incoming-response`, and `response-outparam` all become `request` and `response`, with `stream<u8>` bodies and a `future` for trailers. The handler is now an `async func`:

```wit
// WASI 0.2
handle: func(request: incoming-request, response-out: response-outparam);

// WASI 0.3
handle: async func(request: request) -> result<response, error-code>;
```

The `proxy` world is replaced by `service`, and a new `middleware` world both imports and exports the handler.
- **`wasi:sockets` drops its `network` resource.** Network access is granted at the world level instead of being threaded through every `bind`, `connect`, and DNS lookup. The seven WASI 0.2 socket interfaces consolidate into one `types` interface plus `ip-name-lookup`, and TCP `listen` returns `stream<tcp-socket>` directly instead of requiring a separate `accept` loop.

Smaller per-interface changes — filesystem methods becoming `async func`, the `wasi:clocks` rename pass (`wall-clock` → `system-clock`, `datetime` → `instant`), the `max-len` rename in `wasi:random`, the new shared `wasi:cli/types` interface — are documented in the WASI.dev page linked above.

## Tooling requirements

| Tool | Minimum | Notes |
| ------------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
| Wasmtime | 43+ for `wasmtime run`; 44+ for `wasmtime serve` | Enable with `-Sp3 -W component-model-async=y`. |
| `wit-bindgen` | 0.46+ | Use the `async` feature for 0.3 binding generation. |
| jco | latest | 0.3 host bindings ship in the `preview3-shim` package. |
| `wkg` | 0.15+ | Required to fetch `wasi:cli@0.3.0-rc-2026-03-15` and related packages. |
| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. |

> **Version pinning.** As of WASI 0.3.0's release on 2026-06-11, Wasmtime and `wit-bindgen` still vendor the `0.3.0-rc-2026-03-15` snapshot of the WIT. Components pinning to the published `0.3.0` will fail to instantiate against current Wasmtime; use the RC pin until those tools refresh.

## Further reading

- [Async, Streams, and Futures](./async.md) — the conceptual foundation
- [Creating Runnable Components in Rust](../language-support/creating-runnable-components/rust.md) — worked Rust example with the 0.3 `async fn run()` pattern
- [WASI 0.3](https://wasi.dev/releases/wasi-p3) on WASI.dev — full WIT-level diff per interface
64 changes: 64 additions & 0 deletions component-model/src/design/wit.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,54 @@ tuple<u64, string, u64> // An integer, then a string, then an integer

This is similar to tuples in Rust or OCaml.

### Streams

`stream<T>` for any type `T` denotes an asynchronous, ordered sequence of values of type `T`.
Unlike a `list<T>`, which contains all of its values at once,
a `stream<T>` delivers values incrementally:
the producer pushes elements as they become available,
and the consumer receives them as they arrive.

```wit
stream<u8> // a stream of bytes
stream<log-entry> // a stream of records
```

A `stream<T>` is a Canonical ABI value, not a resource:
it can be returned from a function, accepted as a parameter,
and passed across component boundaries the same way a primitive type is.
A middle component can also hand a stream along without consuming it.

Streams were added for WASI 0.3 and are most often paired with a [`future`](#futures)
that signals when the stream has terminated;
the stream-plus-future tuple shape appears throughout
[`wasi:filesystem`](https://github.com/WebAssembly/WASI/tree/main/proposals/filesystem),
[`wasi:cli`](https://github.com/WebAssembly/WASI/tree/main/proposals/cli),
and [`wasi:sockets`](https://github.com/WebAssembly/WASI/tree/main/proposals/sockets).

For the underlying motivation, see [Async, Streams, and Futures](./async.md).

### Futures

`future<T>` for any type `T` is a typed handle for a single value of type `T`
that will become available later.
A function returning a `future<T>` does not block on the value being produced;
the caller awaits the future when it needs the value.

```wit
future<u32> // a future that will resolve to a u32
future<result<response, error-code>> // a future that will resolve to either a response or an error
```

Like `stream<T>`, `future<T>` is a Canonical ABI value rather than a resource,
so it travels across component boundaries the same way primitives do.

Futures were added for WASI 0.3.
They are similar to a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) in JavaScript
or a [`std::future::Future`](https://doc.rust-lang.org/std/future/trait.Future.html) in Rust.

For the underlying motivation, see [Async, Streams, and Futures](./async.md).

## User-defined types

New domain-specific types can be defined within an `interface` or `world`.
Expand Down Expand Up @@ -393,6 +441,22 @@ get-customers-paged: func(cont: continuation-token) -> tuple<list<customer>, con
A function can be declared inside an [interface](#interfaces),
or can be declared as an import or export in a [world](#worlds).

### Asynchronous functions

A function can be declared `async`, indicating that the call may suspend before producing its result:

```wit
// An async function returning a result
handle: async func(request: request) -> result<response, error-code>;
```

The runtime owns the scheduling; from the WIT perspective, the call looks like an ordinary function.
Bindings generators emit each side in the host language's natural async idiom:
an `async fn` in Rust, a `Promise`-returning function in JavaScript, and so on.

Asynchronous functions were added for WASI 0.3, alongside [`stream<T>`](#streams) and [`future<T>`](#futures).
For the underlying motivation, see [Async, Streams, and Futures](./async.md).

## Interfaces

An interface is a named set of types and functions,
Expand Down
Loading