docs(wit): document capability and component contracts
This commit is contained in:
@@ -1,4 +1,12 @@
|
|||||||
|
//! Example service that imports the versioned Wasmeld monotonic-clock capability.
|
||||||
|
//!
|
||||||
|
//! Each response is eight little-endian timestamp bytes followed by the input.
|
||||||
|
//! The timestamp is Actor-local monotonic time, not a Unix timestamp.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// This macro generates Rust ABI adapters at compile time from the
|
||||||
|
// materialized local WIT graph. It does not make a remote protocol call or
|
||||||
|
// embed the WIT source files; the Component carries canonical ABI types.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "clock-probe-component",
|
world: "clock-probe-component",
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ schema_version = 1
|
|||||||
name = "wasmeld:clock"
|
name = "wasmeld:clock"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/clock"
|
source = "path+../../wit/clock"
|
||||||
sha256 = "93079c69d1b9cb3afc28da6fe4a7e37f724d0a09e780c847f8524d3920a90960"
|
sha256 = "43ece485a550eca94cf06cb711f0b42c9f4977d275e9a042c3ac84eeb1f941e1"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package component:clock-probe@0.1.0;
|
package component:clock-probe@0.1.0;
|
||||||
|
|
||||||
|
/// Base service exports plus the exact monotonic-clock Host capability used by
|
||||||
|
/// this Component. Other Wasmeld capabilities are not linked implicitly.
|
||||||
world clock-probe-component {
|
world clock-probe-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
import wasmeld:clock/monotonic-clock@0.1.0;
|
import wasmeld:clock/monotonic-clock@0.1.0;
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
//! Minimal stateful Component used to demonstrate a warm Actor Store.
|
||||||
|
//!
|
||||||
|
//! Invoke returns an eight-byte little-endian counter. The value survives
|
||||||
|
//! calls to the same Actor but resets on `init`, restart, or process recovery.
|
||||||
|
|
||||||
use core::sync::atomic::{AtomicU64, Ordering};
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// Bindings are generated from `wit/world.wit` plus materialized
|
||||||
|
// `wit/deps`; WIT is a compile-time ABI contract, not downloaded at run
|
||||||
|
// time and not executed as code.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "counter-component",
|
world: "counter-component",
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ schema_version = 1
|
|||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package component:counter@0.1.0;
|
package component:counter@0.1.0;
|
||||||
|
|
||||||
|
/// Counter only needs the base service lifecycle and imports no Host capability.
|
||||||
world counter-component {
|
world counter-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
|
//! Smallest request/response Component example: it returns input bytes unchanged.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// Generate the guest trait and export adapter from the selected WIT world.
|
||||||
|
// Dependency sync must populate `wit/deps` before this macro is compiled.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "echo-component",
|
world: "echo-component",
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ schema_version = 1
|
|||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package component:echo@0.1.0;
|
package component:echo@0.1.0;
|
||||||
|
|
||||||
|
/// Echo only needs the base service lifecycle and imports no Host capability.
|
||||||
world echo-component {
|
world echo-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
|
//! Deliberately trapping Component used to verify Actor fault isolation.
|
||||||
|
//!
|
||||||
|
//! Sending the exact bytes `fault` triggers a panic. This crate is a test
|
||||||
|
//! fixture and must not be deployed as an application service.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// Compile the versioned WIT world into canonical ABI Rust bindings.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "fault-component",
|
world: "fault-component",
|
||||||
@@ -13,6 +19,8 @@ impl bindings::Guest for Fault {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
fn invoke(input: Vec<u8>) -> Result<Vec<u8>, bindings::ServiceError> {
|
||||||
|
// A panic becomes a Wasm trap so Runtime tests can assert that one
|
||||||
|
// faulted Actor does not corrupt other service instances.
|
||||||
assert_ne!(input, b"fault", "intentional test fault");
|
assert_ne!(input, b"fault", "intentional test fault");
|
||||||
Ok(input)
|
Ok(input)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ schema_version = 1
|
|||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package component:fault@0.1.0;
|
package component:fault@0.1.0;
|
||||||
|
|
||||||
|
/// Fault fixture exports only the base service lifecycle.
|
||||||
world fault-component {
|
world fault-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
|
//! Example service that exercises persistent, service-scoped Host KV.
|
||||||
|
//!
|
||||||
|
//! Input uses the test protocol `get:<key>`, `set:<key>:<value>`, or
|
||||||
|
//! `delete:<key>`. Production Components should define an application-specific
|
||||||
|
//! request format instead of copying this colon-delimited probe protocol.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// `generate_all` emits both the service export and imported KV module from
|
||||||
|
// the exact versions selected in `wit/world.wit`.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "kv-probe-component",
|
world: "kv-probe-component",
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ schema_version = 1
|
|||||||
name = "wasmeld:kv"
|
name = "wasmeld:kv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/kv"
|
source = "path+../../wit/kv"
|
||||||
sha256 = "6dc3a84dc03c7104aa8b305518466c95e4440e1031c4bef3b59843f40f24d0fb"
|
sha256 = "10dc81b1f04bd9c8f7b324a96e0c1d24d5af0c2dabfc723d003b8729cfd933d9"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package component:kv-probe@0.1.0;
|
package component:kv-probe@0.1.0;
|
||||||
|
|
||||||
|
/// Base service exports plus the exact persistent KV Host capability used by
|
||||||
|
/// this Component.
|
||||||
world kv-probe-component {
|
world kv-probe-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
import wasmeld:kv/store@0.1.0;
|
import wasmeld:kv/store@0.1.0;
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
|
//! Resident event/effect fixture covering every current Host-driven I/O family.
|
||||||
|
//!
|
||||||
|
//! The callback echoes stream chunks and datagrams, rearms fired timers,
|
||||||
|
//! acknowledges messages, and reflects extension commands. It owns no socket,
|
||||||
|
//! timer, or subscription handles; those resources remain in the Host driver.
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// One generated module contains both the base service export and resident
|
||||||
|
// actor export because this component world composes both WIT packages.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "resident-probe-component",
|
world: "resident-probe-component",
|
||||||
@@ -35,6 +43,8 @@ impl ResidentGuest for ResidentProbe {
|
|||||||
fn handle_event(
|
fn handle_event(
|
||||||
input: Event,
|
input: Event,
|
||||||
) -> Result<Vec<Effect>, bindings::exports::wasmeld::resident::actor::ResidentError> {
|
) -> Result<Vec<Effect>, bindings::exports::wasmeld::resident::actor::ResidentError> {
|
||||||
|
// Event callbacks are serialized with normal invocations by the same
|
||||||
|
// Actor mailbox, so this in-memory count is deterministic per Actor.
|
||||||
EVENTS_HANDLED.fetch_add(1, Ordering::Relaxed);
|
EVENTS_HANDLED.fetch_add(1, Ordering::Relaxed);
|
||||||
Ok(match input {
|
Ok(match input {
|
||||||
Event::StreamData(chunk) => vec![Effect::WriteStream(StreamWrite {
|
Event::StreamData(chunk) => vec![Effect::WriteStream(StreamWrite {
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ schema_version = 1
|
|||||||
name = "wasmeld:resident"
|
name = "wasmeld:resident"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/resident"
|
source = "path+../../wit/resident"
|
||||||
sha256 = "683a3c7196a6dd77b3f619227aa093430ff6995c097d39bb82d501b7c59a196c"
|
sha256 = "a13728b0e6f000c03eb0f88e2e54bad1c6e5160ac3ca4b9e87619247c0043d1b"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package component:resident-probe@0.1.0;
|
package component:resident-probe@0.1.0;
|
||||||
|
|
||||||
|
/// Composes ordinary request/response exports with the optional resident actor
|
||||||
|
/// export. Network and timer drivers remain Host-owned.
|
||||||
world resident-probe-component {
|
world resident-probe-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
export wasmeld:resident/actor@0.1.0;
|
export wasmeld:resident/actor@0.1.0;
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
|
//! CPU-bound fixture used to verify fuel and epoch deadline interruption.
|
||||||
|
//!
|
||||||
|
//! The input's first eight bytes select a little-endian iteration count.
|
||||||
|
//! Initializing with `spin` intentionally runs without a finite loop bound.
|
||||||
|
//! This crate is a Runtime test fixture, not an application service.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// Compile the base service WIT contract into guest and export adapters.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "spin-component",
|
world: "spin-component",
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ schema_version = 1
|
|||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package component:spin@0.1.0;
|
package component:spin@0.1.0;
|
||||||
|
|
||||||
|
/// Spin fixture exports only the base service lifecycle.
|
||||||
world spin-component {
|
world spin-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
|
//! Fixture proving that ambient WASI clock imports are denied by the sandbox.
|
||||||
|
//!
|
||||||
|
//! Calling `std::time::Instant::now` causes this Component to import WASI
|
||||||
|
//! monotonic-clock directly. Wasmeld only links explicitly approved capability
|
||||||
|
//! packages, so registration or startup must reject this fixture.
|
||||||
|
|
||||||
mod bindings {
|
mod bindings {
|
||||||
|
// The declared service WIT contains no WASI clock capability; the direct
|
||||||
|
// standard-library use below deliberately creates an undeclared import.
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "wasi-clock-probe-component",
|
world: "wasi-clock-probe-component",
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ schema_version = 1
|
|||||||
name = "wasmeld:service"
|
name = "wasmeld:service"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "path+../../wit/service"
|
source = "path+../../wit/service"
|
||||||
sha256 = "d5497307bbcd1e159f7707f385b488a6f2e26362d5256d5bc1080ac603a51305"
|
sha256 = "1b2069606ccbf5202789667570bc824f702f34b147aa38e81c2ab677444ffa0e"
|
||||||
replaced = true
|
replaced = true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package component:wasi-clock-probe@0.1.0;
|
package component:wasi-clock-probe@0.1.0;
|
||||||
|
|
||||||
|
/// The declared world grants only the base service contract. Any direct WASI
|
||||||
|
/// clock import introduced by implementation code remains undeclared.
|
||||||
world wasi-clock-probe-component {
|
world wasi-clock-probe-component {
|
||||||
include wasmeld:service/service-component@0.1.0;
|
include wasmeld:service/service-component@0.1.0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
package wasmeld:clock@0.1.0;
|
package wasmeld:clock@0.1.0;
|
||||||
|
|
||||||
|
/// Actor-local monotonic time for measuring elapsed durations.
|
||||||
interface monotonic-clock {
|
interface monotonic-clock {
|
||||||
|
/// Returns nanoseconds elapsed since this Actor Store was created.
|
||||||
|
///
|
||||||
|
/// The value is monotonic and saturates at `u64::MAX`. It is not Unix time,
|
||||||
|
/// cannot be compared across Actor restarts, and must not be persisted as a
|
||||||
|
/// wall-clock timestamp.
|
||||||
now: func() -> u64;
|
now: func() -> u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Host-side world used by Wasmeld to link the clock implementation.
|
||||||
world clock-host {
|
world clock-host {
|
||||||
import monotonic-clock;
|
import monotonic-clock;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,37 @@
|
|||||||
package wasmeld:kv@0.1.0;
|
package wasmeld:kv@0.1.0;
|
||||||
|
|
||||||
|
/// Small persistent byte-value store scoped to the stable service ID.
|
||||||
|
///
|
||||||
|
/// Revisions of the same service share a namespace, so a deployment change
|
||||||
|
/// preserves values. Keys are non-empty UTF-8 strings of at most 256 bytes and
|
||||||
|
/// values are at most 64 KiB in this version of the Host implementation.
|
||||||
|
/// Operations are not multi-key transactions.
|
||||||
interface store {
|
interface store {
|
||||||
|
/// Expected validation or backend failures.
|
||||||
variant kv-error {
|
variant kv-error {
|
||||||
|
/// The key is empty or exceeds the Host byte limit.
|
||||||
invalid-key(string),
|
invalid-key(string),
|
||||||
|
/// The attempted or stored value exceeds the Host byte limit.
|
||||||
value-too-large(u64),
|
value-too-large(u64),
|
||||||
|
/// Persistence failed, timed out, or was temporarily unavailable.
|
||||||
|
///
|
||||||
|
/// A timeout is not cancellation: a mutation may finish after the caller
|
||||||
|
/// receives this error. Retried `set` and `delete` operations should be
|
||||||
|
/// idempotent.
|
||||||
operation-failed(string),
|
operation-failed(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current value, or `none` when the key does not exist.
|
||||||
get: func(key: string) -> result<option<list<u8>>, kv-error>;
|
get: func(key: string) -> result<option<list<u8>>, kv-error>;
|
||||||
|
|
||||||
|
/// Creates or replaces one value.
|
||||||
set: func(key: string, value: list<u8>) -> result<_, kv-error>;
|
set: func(key: string, value: list<u8>) -> result<_, kv-error>;
|
||||||
|
|
||||||
|
/// Removes one value; deleting a missing key succeeds.
|
||||||
delete: func(key: string) -> result<_, kv-error>;
|
delete: func(key: string) -> result<_, kv-error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Host-side world used by Wasmeld to link persistent KV.
|
||||||
world kv-host {
|
world kv-host {
|
||||||
import store;
|
import store;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,43 +5,61 @@ package wasmeld:resident@0.1.0;
|
|||||||
/// and all operating-system handles. A Component handles one bounded event and
|
/// and all operating-system handles. A Component handles one bounded event and
|
||||||
/// returns effects before control goes back to the Runtime.
|
/// returns effects before control goes back to the Runtime.
|
||||||
interface actor {
|
interface actor {
|
||||||
|
/// Opaque, nonzero identity allocated by the Host for one resident session.
|
||||||
|
///
|
||||||
|
/// IDs are scoped to one service revision and session. They are never OS
|
||||||
|
/// handles, must not be guessed, and become invalid after Host release.
|
||||||
type resource-id = u64;
|
type resource-id = u64;
|
||||||
|
|
||||||
|
/// One occurrence of a previously registered and armed timer.
|
||||||
record timer-fired {
|
record timer-fired {
|
||||||
timer-id: resource-id,
|
timer-id: resource-id,
|
||||||
|
/// Driver monotonic deadline in nanoseconds, not wall-clock time.
|
||||||
scheduled-at-ns: u64,
|
scheduled-at-ns: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A stream accepted by a previously registered TCP or Unix endpoint.
|
||||||
record stream-opened {
|
record stream-opened {
|
||||||
endpoint-id: resource-id,
|
endpoint-id: resource-id,
|
||||||
stream-id: resource-id,
|
stream-id: resource-id,
|
||||||
|
/// Driver-formatted peer address when the transport provides one.
|
||||||
peer: option<string>,
|
peer: option<string>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One bounded read chunk. Chunk boundaries are not message boundaries.
|
||||||
record stream-chunk {
|
record stream-chunk {
|
||||||
stream-id: resource-id,
|
stream-id: resource-id,
|
||||||
bytes: list<u8>,
|
bytes: list<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Terminal reason reported by the Host driver.
|
||||||
enum close-reason {
|
enum close-reason {
|
||||||
|
/// The peer closed the transport normally.
|
||||||
peer-closed,
|
peer-closed,
|
||||||
|
/// The platform or Component requested closure.
|
||||||
host-closed,
|
host-closed,
|
||||||
|
/// The configured inactivity deadline elapsed.
|
||||||
idle-timeout,
|
idle-timeout,
|
||||||
|
/// Protocol validation failed above the transport layer.
|
||||||
protocol-error,
|
protocol-error,
|
||||||
|
/// The operating-system transport returned an error.
|
||||||
transport-error,
|
transport-error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Final event for a stream identity.
|
||||||
record stream-closed {
|
record stream-closed {
|
||||||
stream-id: resource-id,
|
stream-id: resource-id,
|
||||||
reason: close-reason,
|
reason: close-reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One UDP datagram. A datagram is never split across events.
|
||||||
record datagram {
|
record datagram {
|
||||||
endpoint-id: resource-id,
|
endpoint-id: resource-id,
|
||||||
peer: string,
|
peer: string,
|
||||||
bytes: list<u8>,
|
bytes: list<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One delivery from a Host-owned broker or internal queue.
|
||||||
record message {
|
record message {
|
||||||
subscription-id: resource-id,
|
subscription-id: resource-id,
|
||||||
message-id: u64,
|
message-id: u64,
|
||||||
@@ -59,65 +77,107 @@ interface actor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
variant event {
|
variant event {
|
||||||
|
/// A registered timer reached its monotonic deadline.
|
||||||
timer(timer-fired),
|
timer(timer-fired),
|
||||||
|
/// A Host driver accepted a new stream.
|
||||||
stream-opened(stream-opened),
|
stream-opened(stream-opened),
|
||||||
|
/// A stream produced one bounded read chunk.
|
||||||
stream-data(stream-chunk),
|
stream-data(stream-chunk),
|
||||||
|
/// The driver can accept writes after previous backpressure.
|
||||||
stream-writable(resource-id),
|
stream-writable(resource-id),
|
||||||
|
/// The peer closed its write half; local writes may still be valid.
|
||||||
stream-half-closed(resource-id),
|
stream-half-closed(resource-id),
|
||||||
|
/// The stream is terminal and its identity will be released.
|
||||||
stream-closed(stream-closed),
|
stream-closed(stream-closed),
|
||||||
|
/// A UDP endpoint received one datagram.
|
||||||
datagram(datagram),
|
datagram(datagram),
|
||||||
|
/// A subscription delivered one message.
|
||||||
message(message),
|
message(message),
|
||||||
|
/// A separately versioned extension source produced an event.
|
||||||
source(source-event),
|
source(source-event),
|
||||||
|
/// The session is stopping. No later events will be delivered.
|
||||||
shutdown,
|
shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request to enqueue bytes for a stream driver.
|
||||||
record stream-write {
|
record stream-write {
|
||||||
stream-id: resource-id,
|
stream-id: resource-id,
|
||||||
bytes: list<u8>,
|
bytes: list<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request to send one datagram through a registered UDP endpoint.
|
||||||
record datagram-send {
|
record datagram-send {
|
||||||
endpoint-id: resource-id,
|
endpoint-id: resource-id,
|
||||||
peer: string,
|
peer: string,
|
||||||
bytes: list<u8>,
|
bytes: list<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request to arm or replace a registered timer schedule.
|
||||||
record timer-arm {
|
record timer-arm {
|
||||||
timer-id: resource-id,
|
timer-id: resource-id,
|
||||||
|
/// Delay from operation application, in milliseconds.
|
||||||
delay-ms: u64,
|
delay-ms: u64,
|
||||||
|
/// Optional repeating interval in milliseconds.
|
||||||
interval-ms: option<u64>,
|
interval-ms: option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Request to acknowledge a previously delivered message.
|
||||||
record message-ack {
|
record message-ack {
|
||||||
subscription-id: resource-id,
|
subscription-id: resource-id,
|
||||||
message-id: u64,
|
message-id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Driver-specific command for a separately versioned extension source.
|
||||||
record source-command {
|
record source-command {
|
||||||
source-id: resource-id,
|
source-id: resource-id,
|
||||||
command: string,
|
command: string,
|
||||||
payload: list<u8>,
|
payload: list<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Host operations requested after handling one event.
|
||||||
|
///
|
||||||
|
/// Effects are declarations, not completed I/O. The Host validates the
|
||||||
|
/// entire returned list before applying any item, then the external driver
|
||||||
|
/// performs the operations in order.
|
||||||
variant effect {
|
variant effect {
|
||||||
|
/// Enqueue bytes for an open stream.
|
||||||
write-stream(stream-write),
|
write-stream(stream-write),
|
||||||
|
/// Begin closing an open stream.
|
||||||
close-stream(resource-id),
|
close-stream(resource-id),
|
||||||
|
/// Stop delivering new read data until resumed.
|
||||||
pause-stream(resource-id),
|
pause-stream(resource-id),
|
||||||
|
/// Resume delivery after a matching pause.
|
||||||
resume-stream(resource-id),
|
resume-stream(resource-id),
|
||||||
|
/// Send one UDP datagram.
|
||||||
send-datagram(datagram-send),
|
send-datagram(datagram-send),
|
||||||
|
/// Arm or replace one timer.
|
||||||
arm-timer(timer-arm),
|
arm-timer(timer-arm),
|
||||||
|
/// Disarm one timer; its identity remains registered.
|
||||||
cancel-timer(resource-id),
|
cancel-timer(resource-id),
|
||||||
|
/// Acknowledge one broker or queue message.
|
||||||
acknowledge-message(message-ack),
|
acknowledge-message(message-ack),
|
||||||
|
/// Forward an extension-specific command.
|
||||||
source-command(source-command),
|
source-command(source-command),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expected Component failure while processing a resident event.
|
||||||
variant resident-error {
|
variant resident-error {
|
||||||
event-failed(string),
|
event-failed(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles exactly one serialized, bounded event.
|
||||||
|
///
|
||||||
|
/// This callback must not run an internal infinite loop or block on network
|
||||||
|
/// I/O. Update Component state, return requested effects, and let the Host
|
||||||
|
/// wait for the next external event. A trap faults the Actor; `event-failed`
|
||||||
|
/// reports an application error without changing the ABI.
|
||||||
handle-event: func(input: event) -> result<list<effect>, resident-error>;
|
handle-event: func(input: event) -> result<list<effect>, resident-error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optional export world for Components that need long-lived Host-driven I/O.
|
||||||
|
///
|
||||||
|
/// A Component may compose this package with `wasmeld:service` and only the
|
||||||
|
/// capability packages it actually imports.
|
||||||
world resident-component {
|
world resident-component {
|
||||||
export actor;
|
export actor;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,34 @@
|
|||||||
package wasmeld:service@0.1.0;
|
package wasmeld:service@0.1.0;
|
||||||
|
|
||||||
|
/// Minimum request/response lifecycle implemented by every Wasmeld service.
|
||||||
|
///
|
||||||
|
/// The Runtime creates one Component Store per Actor, calls `init` once, then
|
||||||
|
/// serializes `invoke` calls through that Actor's bounded mailbox. Component
|
||||||
|
/// memory can therefore remain warm between calls, but it is not durable and
|
||||||
|
/// is lost when the Actor or process restarts.
|
||||||
world service-component {
|
world service-component {
|
||||||
|
/// Application-level failures returned without trapping the Component.
|
||||||
|
///
|
||||||
|
/// Traps, fuel exhaustion, deadline interruption, and invalid ABI data are
|
||||||
|
/// Runtime failures and do not use this variant.
|
||||||
variant service-error {
|
variant service-error {
|
||||||
|
/// The Actor cannot accept calls because its initial configuration failed.
|
||||||
init-failed(string),
|
init-failed(string),
|
||||||
|
/// This input could not be processed; a later call may still succeed.
|
||||||
call-failed(string),
|
call-failed(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initializes a newly created Actor Store.
|
||||||
|
///
|
||||||
|
/// `config` is opaque to the platform. Return `init-failed` for expected
|
||||||
|
/// configuration errors; do not retain borrowed views into this byte list.
|
||||||
export init: func(config: list<u8>) -> result<_, service-error>;
|
export init: func(config: list<u8>) -> result<_, service-error>;
|
||||||
|
|
||||||
|
/// Processes one opaque request and returns one opaque response.
|
||||||
|
///
|
||||||
|
/// The management API represents these bytes as base64, while the public
|
||||||
|
/// gateway transports them as `application/octet-stream`. The Component
|
||||||
|
/// should return promptly and use the resident actor contract for long-lived
|
||||||
|
/// I/O instead of blocking this call in an internal loop.
|
||||||
export invoke: func(input: list<u8>) -> result<list<u8>, service-error>;
|
export invoke: func(input: list<u8>) -> result<list<u8>, service-error>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user