Resource

target

One workload, on one interface of it, that this task's calls go to for as long as the handle is alive.

A handle is per interface, not per workload, because a workload need only export some of what a plugin imports. Binding both together is what makes the pairing checkable when it is made rather than when it is used: a handle exists only if that workload really serves that interface, so holding one is proof the call can be routed.

Handles nest per interface: the innermost live handle for the interface being called wins, and dropping it restores the one it shadowed. A handle for one interface does not redirect calls on another — those fall back to the workload whose capability call this task is serving, the same as if no handle were held, so a plugin calling back into its own caller needs no handle at all.

A handle also pins which component serves the interface, so a call it scopes cannot be moved to another mid-dispatch. It does not pin the workload: one held past the workload's stop — or past a redeploy under the same id, which is a different workload as far as this interface is concerned — answers not-running on the next call through it. Open a handle for the work at hand rather than stashing one.

resource target;

F get-workload-id

get-workload-id: func() -> workload-id;

The workload this handle names.

F get-interface

get-interface: func() -> string;

The interface this handle routes.

F open

open: func(id: workload-id, interface: string) -> option​<target>;

Direct this task's calls on interface to id, or none when that workload is not currently callable on it — it is not running, or it does not export that interface. %interface is spelled as it appears in callable, which is where the pairs come from.

Check this rather than assuming: what callable returns is a snapshot, and a workload can stop between reading it and acting on it. A plugin's own wasi:cli/run dispatch loop races exactly that way every time a workload is undeployed.