Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
63dc70c
docs(design): add Async, Streams, and Futures concepts page
ericgregory Jun 12, 2026
3ea8111
docs(design): add Migrating from WASI P2 to WASI P3 guide
ericgregory Jun 12, 2026
58a60a2
Improve clarity in migrating to WASI P3 documentation
ericgregory Jun 12, 2026
fc2a837
docs(design): reframe async page as P3-only, trim duplication
ericgregory Jun 12, 2026
c91de06
docs: rename WASI P2/P3 to WASI 0.2/0.3 in design pages
ericgregory Jun 16, 2026
35629a7
docs(design/migrating-to-p3): fix awkward bare-decimal references
ericgregory Jun 16, 2026
ffec37a
Update component-model/src/design/async.md
ericgregory Jul 2, 2026
c56583f
Update component-model/src/design/async.md
ericgregory Jul 2, 2026
3b49882
Update component-model/src/design/async.md
ericgregory Jul 2, 2026
2695ed4
Update component-model/src/design/async.md
ericgregory Jul 2, 2026
edd2d2d
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
84aab9e
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
707b97e
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
c041079
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
120f456
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
9860cc1
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
58aabb2
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
2275fbf
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
63df4c1
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
2d0f794
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
d43e89e
Apply suggestion from @vados-cosmonic
ericgregory Jul 2, 2026
3421c7a
docs(design): address review feedback on async and migration pages
ericgregory Jul 2, 2026
7a185d4
docs(design/wit-example): add WASI P3 CLI example
ericgregory Jun 16, 2026
54a9fc6
docs(design/wit-example): rename WASI P2/P3 to WASI 0.2/0.3
ericgregory Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
84 changes: 84 additions & 0 deletions component-model/src/design/async.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Native Async with WASI 0.3

WASI 0.3 adds new Canonical ABI primitives to the Component Model that enable async functionality. Components that target WASI 0.3 can use the new features in their WIT files:
* `async func`
* `stream<T>`
* `future<T>`

These new types 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 a closer look at the WASI 0.3 release, including a 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.

> [!NOTE]
> WASI 0.3 builds on WASI 0.2 rather than replacing it. Runtimes can host both versions side by side, and a 0.3 host can polyfill 0.2 imports at the boundary, so applications can migrate incrementally as toolchains and dependencies land 0.3 support.

## The async problem that WASI 0.3 solves

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 async primitives help close this expressivity gap. With updated Component ABI mechanics that enable `async func`, `stream<T>`, and `future<T>` available at the WIT level, scheduling and wake-up propagation become the runtime's job rather than any individual component's.

Components can pass futures and streams along without keeping their own event loops running to relay readiness, as was necessary with WASI 0.2.

## Async functions, Streams, and Futures

### Async Functions (`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.

```diff
- handle: func(request: request) -> result<response, error-code>;
+ 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.

### Streams (`stream<T>`)

A typed, asynchronous channel for a sequence of `T` values. Crucially, `stream<T>` is a Canonical ABI *value*, not a resource (as opposed to WASI 0.2) -- 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 one or more intermediate components without those components having to relay any wake-ups.

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

### Futures (`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.

Note that synchronous functions which return `future<T>`s *cannot* block; the caller can await the result when it needs it.

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

## A look at async patterns in WASI 0.3

### Stream plus terminal future

Reads return both a data channel and a completion handle, packed into a tuple ([`read-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L308) in `wasi-filesystem`):

```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 ([`write-via-stream`](https://github.com/WebAssembly/wasi-filesystem/blob/main/wit/types.wit#L320) in `wasi-filesystem`):

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

New Component Model primitives that enable use of [`async func`, `stream<T>`, and `future<T>`](./async.md), were 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.2 can be used in hosts just as before, WASI 0.3 is a purely additive change.

Separately, 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#the-async-problem-that-wasi-03-solves) 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 | 46+ | WASI 0.3 and `component-model-async` are on by default. |
| `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` and related packages. |
| Rust | nightly | Current stable bundles a `wasm-component-ld` too old for 0.3 outputs of `wit-bindgen` 0.58. |

## 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
88 changes: 86 additions & 2 deletions component-model/src/design/wit-example.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# WIT By Example

This section includes two examples to introduce WIT:
a simpler "clocks" example and a more complicated "filesystems" example.
This section includes three examples to introduce WIT: a simple "clocks" example, a more elaborate "filesystems" example, and a WASI 0.3 "CLI" example that introduces async functions, streams, and futures.

For a full WIT reference, see [the next section](./wit.md).

## Clocks
Expand Down Expand Up @@ -197,6 +197,90 @@ open-at: func(

`open-at()` returns a new descriptor, given a path string and flags.

## WASI 0.3 CLI

The two examples above use WIT features that have been part of the language since WASI 0.2.
WASI 0.3 added three new primitives to the Component Model's Canonical ABI:
[`async func`, `stream<T>`, and `future<T>`](./async.md).
This example walks through a simplified version of the
[`wasi:cli`](https://github.com/WebAssembly/WASI/tree/main/proposals/cli) package,
which exercises all three.

### Async functions

A function declared `async` may suspend before returning a result.
The runtime owns the scheduling; the guest sees an ordinary call and the host sees no busy loop:

```wit
package wasi-example:cli;

interface run {
run: async func() -> result;
}
```

The `result` return type with no parameters means "either success or failure, with no value attached to either."
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.

### Streams plus terminal futures

Reading from standard input pairs a `stream<T>` with a `future`:

```wit
interface stdin {
use types.{error-code};
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
}
```

The `stream<u8>` delivers bytes incrementally.
The `future` resolves once the operation has terminated, carrying either success
(the underscore means "no value attached") or an `error-code` (defined as an `enum` in the `types` interface).
The two halves are independent:
the caller can consume the stream eagerly, sample it, or drop it part-way through,
and the future signals the outcome either way.

Unlike resources, `stream<T>` and `future<T>` are *values*.
They can be returned from functions, accepted as parameters,
and passed across component boundaries the same way primitive types are.

### Inverted writes

Writing to standard output reverses the direction.
Instead of the host handing the guest a resource to write into,
the guest supplies its data as a `stream<u8>` parameter
and the host returns a `future` that resolves once the bytes are consumed:

```wit
interface stdout {
use types.{error-code};
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
}
```

This shape — stream parameter, future return — appears throughout WASI 0.3 wherever a guest
writes data: stdout, stderr, filesystem writes, and TCP sends all follow it.

### Aggregating into a world

A world ties imports and exports together.
The `command` world below imports the I/O interfaces and exports `run`:

```wit
world command {
import stdin;
import stdout;
export run;
}
```

A component implementing this world supplies an implementation of `run`,
and from inside that implementation may call `read-via-stream` on stdin and `write-via-stream` on stdout.

For a deeper look at the three primitives, including the composition story that motivated adding them,
see [Async, Streams, and Futures](./async.md).

## Further reading

We've seen how using rich types, WIT can encode a multitude
Expand Down
Loading