Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 15 additions & 0 deletions .topiary/languages.ncl
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
languages = {
markdown = {
grammar.source = {
git = {
git = "https://github.com/tree-sitter-grammars/tree-sitter-markdown.git",
rev = "a0a00f817d02412bd92c54d316f164d827b57b5c",
subdir = "tree-sitter-markdown",
},
},
queries.injections.source.path = "queries/injections.scm",
},
wit.indent = " ", # 4 spaces

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The repo has a mix of 2 and 4 space indentation for WIT and this helps align it.

},
}
8 changes: 8 additions & 0 deletions .topiary/queries/injections.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
(fenced_code_block
(info_string
(language) @injection.language) @info
(code_fence_content) @injection.content
; mdBook inline macro
(#not-match? @info ".*(nofmt|rust).*")
(#not-match? @injection.content "\\{\\{\\#include")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used to skip mdbook directives

)
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package foo:wasi-http-service;

world target-world {
include wasi:http/proxy@0.2.3;
include wasi:http/proxy@0.2.3;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

up to us what indentation we prefer, this can be set in .topiary/languages.ncl similar to the indent key here:
https://github.com/topiary/topiary/blob/c72efbfd164f98e5d2fab70a7c984280e0a90378/topiary-config/languages.ncl#L284-L294

}
4 changes: 2 additions & 2 deletions component-model/examples/example-host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ The `adder` world exports an interface called `add` which defines an function th
package docs:adder@0.1.0;

interface add {
add: func(x: u32, y: u32) -> u32;
add: func(x: u32, y: u32) -> u32;
}

world adder {
export add;
export add;
}
```

Expand Down
19 changes: 9 additions & 10 deletions component-model/examples/tutorial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,32 @@ has an `add` operation:
```wit adder
package docs:adder@0.1.0;


interface add {
add: func(x: u32, y: u32) -> u32;
add: func(x: u32, y: u32) -> u32;
}

world adder {
export add;
export add;
}
```

```wit calculator
package docs:calculator@0.1.0;

interface calculate {
enum op {
add,
}
eval-expression: func(op: op, x: u32, y: u32) -> u32;
enum op {
add,
}
eval-expression: func(op: op, x: u32, y: u32) -> u32;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice if we defaulted to a space between items inside an interface.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this apply to worlds as well? We can do only a subset of each as well:
https://github.com/bytecodealliance/tree-sitter-wit/blob/f777cdbe11281ccc68ffa30bd7ea34cdf4ddbec6/grammar.js#L139-L146

    world_definition: ($) =>
      choice(
        $.export_item,
        $.import_item,
        $.use_item,
        $.typedef_item,
        $.include_item,
      ),
    // ...
    _interface_definition: ($) =>
      choice(
        $.use_item,
        seq(optional($.external_id), choice($.typedef_item, $.func_item)),
      ),

}

world calculator {
export calculate;
import docs:adder/add;
export calculate;
import docs:adder/add;
}

world app {
import calculate;
import calculate;
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,5 @@ world revup {
// <namespace>:<package>/<interface>@<package version>
//
import example:string-reverse/reverse@0.1.0;

export reversed-upper;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package docs:adder@0.1.0;

interface add {
variant computation-error {
overflow
overflow,
}
add: func(x: u32, y: u32) -> result<u32, computation-error>;
}
Expand Down
3 changes: 1 addition & 2 deletions component-model/examples/tutorial/tinygo/adder/world2.wit
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@ interface add {

world adder {
include wasi:cli/imports@0.2.0;

export add;
}
Comment on lines 8 to -11

@mkatychev mkatychev Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

up to us if we want to preserve double newlines between certain items

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah let's have newlines between this, but only for worlds? I like the idea of separating the imports, includes and exports in a world declaration

}
2 changes: 1 addition & 1 deletion component-model/examples/tutorial/wit/adder/world.wit
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ interface add {

world adder {
export add;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ interface wall-clock {
seconds: u64,
nanoseconds: u32,
}

now: func() -> datetime;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ interface types {
open-at: func(
path: string,
) -> result<descriptor, error-code>;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ default_registry = "ghcr.io"
[namespace_registries]
# Tell wkg that packages with the `wasi` namespace are in an OCI registry
# under ghcr.io/webassembly
wasi = { registry = "wasi", metadata = { preferredProtocol = "oci", "oci" = {registry = "ghcr.io", namespacePrefix = "webassembly/" } } }
wasi = { registry = "wasi", metadata = { preferredProtocol = "oci", "oci" = { registry = "ghcr.io", namespacePrefix = "webassembly/" } } }
```

As a more generic example, the following configuration instructs `wkg` to use
Expand All @@ -89,7 +89,7 @@ default_registry = "ghcr.io"

[namespace_registries]
# Instruct wkg to use the OCI protocol to fetch packages with the `docs` namespace from ttl.sh/wasm-components
docs = { registry = "docs", metadata = { preferredProtocol = "oci", "oci" = {registry = "ttl.sh", namespacePrefix = "wasm-components/" } } }
docs = { registry = "docs", metadata = { preferredProtocol = "oci", "oci" = { registry = "ttl.sh", namespacePrefix = "wasm-components/" } } }
```

> Note: the registry name can be referenced in the `package_registry_overrides` section of the `wkg` config
Expand Down
20 changes: 10 additions & 10 deletions component-model/src/design/async.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# 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`
* `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 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.
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.

Expand All @@ -22,7 +22,7 @@ The Component Model's Canonical ABI defines how typed values cross component bou

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

Expand All @@ -41,21 +41,21 @@ Code generated from the WIT picks up each language's natural async idiom: `async

### 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.
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
```wit nofmt
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.
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
```wit nofmt
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

Expand All @@ -65,7 +65,7 @@ write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;

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
```wit nofmt
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
```

Expand All @@ -75,7 +75,7 @@ The two halves are independent. The caller can consume the stream eagerly, sampl

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
```wit nofmt
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
```

Expand Down
8 changes: 4 additions & 4 deletions component-model/src/design/migrating-to-p3.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ WASI 0.3 replaces every `wasi:io` resource with a Canonical ABI primitive. The t

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

@mkatychev mkatychev Oct 6, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nofmt is picked up by this query to skip WIT fragments:

(#not-match? @info ".*(nofmt|rust).*")

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

Expand All @@ -47,7 +47,7 @@ In WASI 0.3, the caller does not have to drain the stream to learn whether the r

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
```wit nofmt
// WASI 0.2: receive an output-stream resource, write into it
get-stdout: func() -> output-stream;

Expand All @@ -59,7 +59,7 @@ write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;

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
```wit nofmt
// 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>;
Expand All @@ -77,7 +77,7 @@ The complete per-interface diff lives on [WASI 0.3](https://wasi.dev/releases/wa
- **`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
```wit nofmt
// WASI 0.2
handle: func(request: incoming-request, response-out: response-outparam);

Expand Down
26 changes: 13 additions & 13 deletions component-model/src/design/wit-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ In this case, declarations are _type declarations_ or _function declarations_.

_Record types_ are one of the possible types that can be declared in WIT.

```wit
```wit nofmt
record datetime {
seconds: u64,
nanoseconds: u32,
Expand All @@ -78,7 +78,7 @@ an unsigned 32-bit integer.

The following declares a function named `now`:

```wit
```wit nofmt
now: func() -> datetime;
```

Expand Down Expand Up @@ -116,7 +116,7 @@ Let's look at some WIT features used in this interface.

### Enums

```wit
```wit nofmt
enum error-code {
access,
bad-descriptor,
Expand Down Expand Up @@ -145,7 +145,7 @@ Let's look at the method declarations one at a time:

#### Reading from files

```wit
```wit nofmt
read: func(
length: filesize,
offset: filesize,
Expand Down Expand Up @@ -189,7 +189,7 @@ The `open-at()` method is a constructor, which we know because
it returns a `descriptor` when it doesn't fail (remember that
these methods are attached to the resource type `descriptor`):

```wit
```wit nofmt
open-at: func(
path: string,
) -> result<descriptor, error-code>;
Expand All @@ -215,7 +215,7 @@ The runtime owns the scheduling; the guest sees an ordinary call and the host se
package wasi-example:cli;

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

Expand All @@ -229,8 +229,8 @@ 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>>>;
use types.{error-code};
read-via-stream: func() -> tuple<stream<u8>, future<result<_, error-code>>>;
}
```

Expand All @@ -254,8 +254,8 @@ 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>>;
use types.{error-code};
write-via-stream: func(data: stream<u8>) -> future<result<_, error-code>>;
}
```

Expand All @@ -269,9 +269,9 @@ The `command` world below imports the I/O interfaces and exports `run`:

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

Expand Down
Loading
Loading