1 - Concepts

Wasmflow terminology and the philosophy that guides the platform.

1.1 - What is Wasmflow?

An overview of Wasmflow.

At a high level, Wasmflow orchestrates code and manages communication between dependencies. That code can exist as WebAssembly modules, external microservices, workers over a queue, or anything else that speaks the Wasmflow protocol.

Wasmflow takes functional and flow-based concepts from projects like Erlang, Rx, FBP, and Haskell and combines them with concepts from Docker and Kubernetes.

A Brief History of Containers

While Docker has become synonymous with containers, they didn’t invent the concept. Virtual machines were the original software containers.

Full OS virtualization is where a host machine runs one or more guest operating systems by replicating every observable aspect of physical hardware as a “virtual machine.” A hypervisor manages these guests and and acts like a “super” operating system. That is, an operating system that operates at a higher privilege.

Hypervisors were the original orchestrators and abstracted hardware away from the OS.

Virtualization let users run multiple different operating systems simultaneously on the same hardware. More importantly though, was that it encapsulated the entire running state of an operating system into an easily distributable – albeit massive – VM image. You could automate the creation of a virtual machine then turn around and deploy that same image across a cluster with the press of a button. Before VMs, you’d frequently find system administrators manually imaging physical hard drives to install in new servers.

VMs were great but they were large and unwieldy. Building, maintaining, and licensing an operating system for every VM was wasteful and slow. The value was high but so was the cost. That’s where os-level containers come into play.

OS-level virtualization – or containerization – is the paradigm where the operating system kernel partitions processes into isolated, virtual environments. It’s not as flexible as a virtual machine – an os-level container must typically run on the same hardware and OS it was compiled for – but it’s much lighter. Containers gave users a lighter, more reusable format to distribute. Companies like Docker further innovated and added the concept of layered, composable images to further maximize reusability.

Containers and container runtimes abstracted the process and its environment away from the host OS.

The industry invented containers because the value of VMs was clear as was the waste. Generating reliable, reproducible artifacts was great. Building the same exact systems repeatedly in slightly different ways wasn’t.

Containers freed us from duplicating effort by abstracting at a deeper level. Now we see different duplication. We’re building and rebuilding the same web applications, microservices, queue workers, workflows, build systems, and mobile apps just to house the small bit of code we want to run.

The next step is containerizing the code itself, separating business logic from generic application logic. This abstraction is the foundation of the “serverless” and functions-as-a-service trend which lets users deploy structured code to someone else’s application. And that’s where this story is still being written. “Serverless” isn’t containerization. It’s a product that exploits the problem. It doesn’t solve it. To solve this abstraction we needed a cross-platform, cross-language, standard unit of distribution and a way to isolate it from its environment. That’s not insurmountable, but it was a large ask. Until 2018.

WebAssembly gives us the power to containerize and orchestrate code.

WebAssembly gave us a standardized, distributable bytecode and a runtime that baked in all the isolation and security that twenty-five years of securing the web taught us. Many languages already compile into it, it can run just about everywhere, and it is showing more potential day-by-day. It’s barely usable and not very capable on its own. We need a runtime.

Why Wasmflow?

Wasmflow embeds many of the best ideas from containerization, serverless, and general software development.

Wasmflow gives you:

  • Security Wasmflow uses WebAssembly to sandbox all dependencies and further restricts functionality to CPU only by default. It isolates memory per-transaction so if an attacker did happen to exploit a component, they can only interact with their own memory.
  • Composability You can compile code into Wasmflow components and connect them together with a manifest. After you’re done? That manifest becomes a collection of new components that you can depend on the same way. It’s components all the way down.
  • Productivity Wasmflow normalizes the interfaces in and out of WebAssembly so everything connects together the same way. That means no more complex integrations and the freedom to swap in and out any dependency with little to no effort.
  • Testability Since every component connects the same way, that also means no complex integrations simply to test code. You can use Wasmflow’s own test runner to rapidly run unit tests on the command line.
  • Maintainability Take any part of a Wasmflow application and scale it out as a microservice without rebuilding anything. Take another piece and turn it into a dozen workers on the other end of a message queue. Every boundary of a code container is a point that an application can be cut, molded, extended or scaled independently.

Wasmflow is an opinionated runtime that lets users turn their code into artifacts that can run as a web server, microservice, worker, CLI app, or on the client without any changes. It was built with security, reusability, and composability at its core.

1.2 - Our Philosophy

Acknowledging the reality of software.

Software is easy to create. Applications are not.

Developers have an endless supply of languages to choose from when starting a project. Java, JavaScript, Python, Ruby, C, C++, C#, Rust, Go, Haskell, PHP, VisualBasic, Swift, Dart, TypeScript, COBOL, Kotlin, Julia, Scala, Lisp, Lua, Fortran, Clojure, Erlang, Perl and the list really does go on and on.

Each language takes a different path to the same destination. Each try to make creating valuable software easy. Not software that is worth a lot of money, mind you. Software that users value. Without users, software is worthless.

The more value you try to provide, the more complex software gets. Every switch, button, option, and feature you add makes it harder to develop the next. We can’t avoid that and we shouldn’t try. We shouldn’t make concessions on what we want because it’s difficult.

Application developers are different from library developers

Application developers engineer solutions for end users while a library developers’s end user is an application developer. Programmers frequently fall on a gradient somewhere between both disciplines and they switch hats as necessary. The root of our problem stems from the variables that both sides have to manage. Library developers have concrete, well-defined contracts with limited scope and they invent algorithms that deliver dense chunks of value. Think decoding an animation frame into individual pixels. The inputs, outputs, and success criteria are clear.

Application developers however, don’t know what success looks like. Application developers operate in a test, measure, and adapt loop. This is fundamentally a different environment to work in. Both types of developers produce immense value, but they do so using the same tools designed for library developers. The infinite capability of most programming languages leads to bespoke implementations that are difficult to adapt and impossible to reuse.

Wasmflow is a platform for application developers

The Wasmflow runtime stitches together libraries without needing a code layer. Rather than integrate a library with source code, you connect it via configuration in schematics. The result is a platform that can be molded instantly without rebuilding or rearchitecting anything. Results are easier to test, measure and scale. In short, it costs less to iterate. Wasmflow absorbs the complexity of a scalable, adaptable platform so you can move faster.

1.3 - Terminology

Providers, modules, components, schematics, oh my!

Components

Wasmflow components are the smallest unit of logic in Wasmflow and can be thought of like a common function in any other platform. Components have input and output ports that can connect to any other component.

Ports

Wasmflow ports are asynchronous streams of (wasmflow) packets. A packet is a versioned unit of data that can hold an internal signal, a successful value (in an intermediary formats) or a failure value.

In everyday terms, Wasmflow’s ports are the inputs and outputs to components. They can represent any kind of data, from simple to complex, asynchronous to synchronous, successful computation to internal error or edge cases. Wasmflow’s ports are what make it possible to connect arbitrary components.

Collections

Wasmflow collections are collections of components. If components are analogous to functions, collections are like libraries that house functions in a package or namespace.

Flows

Wasmflow flows are configuration-based components. You take other components, connect them together, and you end up with a new component that can connect the same way.

Runtime

The Wasmflow runtime is the library implementation that manages resolving, loading, validating, and instantiating everything that Wasmflow needs to run.

Hosts

A Wasmflow host is a running implementation of the runtime that speaks Wasmflow RPC protocols. A host embeds the runtime and is what you execute and interact with.

WaPC

The WebAssembly Procedure Call project is an open source standard for communicating in and out of WebAssembly modules. Wasmflow uses WaPC concepts and is committed to progressing open standards in WebAssembly.

Apex

The Apex IDL is a minimal language for defining the interface of your components (among other things). It is loosely based on GraphQL and is generic enough to use for non-WebAssembly purposes.

Manifests & .wafl files

Wasmflow manifests are configuration files that define flow-based components and host configurations.

WebAssembly Module

A “WebAssembly Module” is a generic term for any compiled WebAssembly but when used in Wasmflow’s context it refers to a WebAssembly implementation of a Wasmflow collection of components.

wasmflow

The wasmflow binary is an executable Wasmflow host.

wafl

wafl (pronounced waffle) is the Wasmflow utility binary. It contains useful subcommands for managing or interacting with Wasmflow hosts or components.

2 - Getting Started

Getting Started with Wasmflow
Tip

Keep the Terminology section on hand to help with any new terms.

2.1 - Installing Wasmflow

How to install wasmflow & wafl

Pre-built binary installation

Head over to wasmflow/releases to get the latest version of the Wasmflow tools for your platform

Building from source

make install will build everything in the Wasmflow monorepo and install any binaries into your ~/.cargo/bin directory.

$ git clone https://github.com/wasmflow/wasmflow
$ cd wasmflow && make install

Now that you’ve got wasmflow and wafl installed, learn how to Create WebAssembly Components or jump straight to Invoking Wasmflow Components.

2.2 - Creating WebAssembly Components

Building your first components from scratch.

2.2.1 - Building Components in Rust

Building your first components in Rust.

2.2.1.1 - Prerequisites for Rust components

Dependencies you may need to install for this guide

Rust & Cargo

To build Rust components you will need the Rust compiler and companion tools installed.

Install rust & cargo via rustup for Windows, Mac, or Linux.

Windows users may need to install the C++ build tools with the “Desktop development with C++” module.

WebAssembly target for Rust

To compile Rust into WebAssembly, you need to install a wasm-* target.

Install both the cross-platform wasm32 target and the WASI (WebAssembly Systems Interface) implementation with this rustup command:

rustup target add wasm32-unknown-unknown wasm32-wasi

Node.js & npm

Wasmflow’s code generators are written in JavaScript to make it as easy as possible for people to contribute generators for new languages.

If you don’t have node.js & npm, install it via nvm on Mac or Linux or nvm-windows on Windows.

tomlq

tomlq is a command line parser for TOML files and Wasmflow uses it to grab information from toml files like Cargo.toml.

Install tomlq with the command

cargo install tomlq

wasmflow-codegen

wasmflow-codegen is Wasmflow’s code generator.

Install wasmflow-codegen via npm install -g @candlecorp/codegen

make on Windows

Wasmflow and generated projects use Makefiles to automate builds and code generation. The easiest way to install make on Windows is via Chocolatey.

$ choco install make

2.2.1.2 - Create a new project

Using wafl to create a new boilerplate project

Start a new project with wafl

$ wafl project new my-project rust

wafl will set up a new directory named my-project in the current directory and will clone a suitable boilerplate project. You can specify a git repository URL in place of the language argument to use your own boilerplate projects.

Note

The default boilerplate project comes with some suggested settings for Visual Studio Code users. Feel free to ignore, change, or remove them. They are not required to build the project.

Your first schema

Take note of the schema found in schemas/my-component.apex

namespace "my-component"

type Inputs {
  input: string
}

type Outputs {
  output: string
}

type Config {

}

This schema defines a component named my-component with one input port named input and one output port named output, both dealing with string data. The schema also defines a Config type that is currently empty.

Let’s change our component name to ‘greet’ so we have something slightly more meaningful. Your new schema should look like this:

namespace "greet"

type Inputs {
  input: string
}

type Outputs {
  output: string
}

type Config {

}

Finally, let’s finish this first step by adding our personal and project details to the Cargo.toml file. You can fill out the name, description, and authors to be anything you want, but the rest of this guide assumes your project is named my-project. Change the built artifact names accordingly if you use something different.

[package]
name = "my-project"
version = "0.0.0"
description = "A cool new project"
authors = ["You <you@email.com>"]
edition = "2018"
license = "BSD-3-Clause"

Next we’ll Build and Run your component.

2.2.1.3 - Build and run your new WebAssembly component

Build and run your WebAssembly component with wasmflow

Build your component.

Build your component by running make

$ make

Make generates source code, builds your WebAssembly module, generates keys, signs your module using wafl and stores both the signed and unsigned artifacts in the /build directory.

Tip

The build process also generates source code from your schema(s). Do not edit any that are prefixed with a “This is generated” warning or your changes will be lost on next build.

Run your component

Use [wasmflow] to load your module and execute a component on the command line, sending the string "my_input" to the input port named input.

$ wasmflow invoke ./build/my_project.signed.wasm greet -- --input="my_input"

If all has gone well, you will see nothing. Our component doesn’t output anything yet so naturally we don’t get any interesting output. To convince yourself that something is actually happening, pass --trace or --debug to [wasmflow] for more output. Remember to add the flag before the -- which separates arguments for [wasmflow] vs arguments destined for the executed module. You should see something similar to the below.

$ wasmflow invoke ./build/my_project.signed.wasm greet --trace -- --input="my_input"

2022-07-01T02:39:01 TRACE Logger initialized
2022-07-01T02:39:01 DEBUG Writing logs to ~/.local/state/wasmflow/logs
2022-07-01T02:39:01 DEBUG load as file location="./build/my_project.signed.wasm"
2022-07-01T02:39:01 TRACE bytes include wasm header? is_wasm=true bytes=[0, 97, 115, 109]
2022-07-01T02:39:01 DEBUG wasi enabled id=TODO config=Permissions { dirs: {} }
2022-07-01T02:39:01 DEBUG Collection permissions params=Permissions { dirs: {} }
2022-07-01T02:39:01 DEBUG WASI configuration params=WasiParams { argv: [], map_dirs: [], env_vars: [], preopened_dirs: [] }
2022-07-01T02:39:01 TRACE wasmtime instance loaded duration_μs=118656
2022-07-01T02:39:01 DEBUG wasmtime initialize duration_μs=118859
2022-07-01T02:39:01 DEBUG Input 'my_input' for argument 'input' is not valid JSON. Wrapping it with quotes to make it a valid string value.
2022-07-01T02:39:01 TRACE wasm invoke target=wafl://__local__.coll/greet
2022-07-01T02:39:01 DEBUG wasm invoke component="greet" id=4042109442 payload={"input": [168, 109, 121, 95, 105, 110, 112, 117, 116]}
2022-07-01T02:39:01 TRACE wasm call finished component="greet" id=4042109442 duration_μs=21125
2022-07-01T02:39:01 TRACE wasm call result component="greet" id=4042109442 result=Ok([])
2022-07-01T02:39:01 TRACE stream complete
Tip

The name of your component is dictated by the namespace definition in your schema. The filename is normalized based on best practices for the target language.

Next we’ll add logic to our component.

2.2.1.4 - Adding logic to your component

How to work with component ports

Open up the newly generated component file at ./src/components/greet.rs and add to it so it looks like this:

pub use crate::components::generated::greet::*;

#[async_trait::async_trait]
impl wasmflow_sdk::v1::ephemeral::BatchedComponent for Component {
    async fn job(
        inputs: Self::Inputs,
        outputs: Self::Outputs,
        _config: Option<Self::Config>,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        let greeting = format!("Hello {}", inputs.input);
        outputs.output.done(greeting)?;
        Ok(())
    }
}

Take note of these lines:

let greeting = format!("Hello {}", inputs.input);
outputs.output.done(greeting)?;

The first line uses Rust’s format!() macro to format our input string into a suitable greeting.

The second line takes that greeting string and pushes it to the output port named output.

Finally, done() sends a packet and closes the port in one command.

Tip

Change the port names in your Apex schema and rebuild your component to see how the code generation reflects the changes.

Build and run your component with the new logic to see the output:

$ make
$ wasmflow invoke ./build/my_project.signed.wasm greet -- --input="my_input"
{"output":{"value":"Hello my_input"}}
Tip

Change the inputs.input expression to something like inputs.input.to_uppercase() to see how modules can act on data as it comes through.

Next we’ll add new components to our collection.

2.2.1.5 - Adding components

Adding additional components to our module

A single component is like a single function. A collection of components is like a library. It’s a collection of related functions.

Before we think about writing code, we have to define the contract first. The contract is a schema that describes the inputs, outputs, and configuration for a component.

Use wafl component new [component name] to create a new schema quickly. Let’s call this component concatenate.

$ wafl component new concatenate
2022-06-20T22:09:04  INFO Creating new schema for concatenate at schemas/concatenate.apex

Edit the Inputs and Outputs in your schema to look like the following.

type Inputs {
  left: string
  right: string
}

type Outputs {
  output: string
}
Note

Without knowing anything about the implementation, can you guess what this component will do? If you guessed that it is going combine two strings together as output, you’re correct!

Of course this scenario is contrived, but it’s a signature part of contract driven development and extends beyond “Hello World” style tutorials. The contract is often more important than the name.

Generate the new code

The wasmflow code generator will generate all the necessary files based off the .apex files found in ./schemas/.

Run it automatically with:

$ make codegen

Add our concatenation logic

Add this rust code to the new src/components/concatenate.rs file.

pub use crate::components::generated::concatenate::*;

#[async_trait::async_trait]
impl wasmflow_sdk::v1::ephemeral::BatchedComponent for Component {
    async fn job(
        inputs: Self::Inputs,
        outputs: Self::Outputs,
        config: Option<Self::Config>,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        outputs
            .output
            .done(format!("{} {}", inputs.left, inputs.right))?;
        Ok(())
    }
}

Build and run our new component

This command looks similar to our last command, but take note that we’re sending data on multiple ports now.

$ make
$ wasmflow invoke ./build/my_project.signed.wasm concatenate -- --left=Hello --right=World
{"output":{"value":"Hello World"}}

Success! Now that we’ve got a module, let’s see how we can turn it into a microservice.

2.2.2 - Building Components in Python

Building your first components in Python.
Note

The guide to Python components is a work in progress.

2.2.3 - Building Components in JavaScript

Building your first components in JavaScript.
Note

The guide to JavaScript components is a work in progress.

2.2.4 - Building Components in Go

Building your first components in go.
Note

The guide to Go components is a work in progress.

2.3 - Creating Native Components

Building native components from scratch.

WebAssembly is the future but we’re not 100% there yet. Tasks that WebAssembly can’t (or won’t do) and functionality that Wasmflow doesn’t provide can still be enabled with native components.

Note

The guide to Native components is a work in progress. Visit the wasmflow repository so see examples of Rust-based native components.

2.4 - Invoking Wasmflow Components

How to invoke Wasmflow’s flow-based or WebAssembly components.

2.4.1 - Running your component via Wasmflow

Use wasmflow on the command line to invoke your components.

The general CLI syntax to invoke a component is:

wasmflow invoke [path or OCI URL] [Component name] -- [Input arguments]

For example:

$ wasmflow invoke reg.candle.run/candle/getting-started greet -- --input="my_input"
{"output":{"value":"Hello my_input"}}

By default, wasmflow won’t print anything but the component’s output. To print logs to the terminal, Pass --trace or --debug to [wasmflow] to print logs to the terminal, e.g.

$ wasmflow invoke reg.candle.run/candle/getting-started greet --trace -- --input="my_input"
2022-07-01T03:13:20 TRACE Logger initialized
2022-07-01T03:13:20 DEBUG Writing logs to /Users/jsoverson/.local/state/wasmflow/logs
2022-07-01T03:13:20 DEBUG load from cache path=/var/folders/pg/h7s94pd90w54hcbjpkvm58240000gn/T/wafl/ocicache/reg_candle_run_candle_getting-started.store
2022-07-01T03:13:20 TRACE bytes include wasm header? is_wasm=true bytes=[0, 97, 115, 109]
2022-07-01T03:13:20 DEBUG wasi enabled id=my-project config=Permissions { dirs: {} }
2022-07-01T03:13:20 DEBUG Collection permissions params=Permissions { dirs: {} }
2022-07-01T03:13:20 DEBUG WASI configuration params=WasiParams { argv: [], map_dirs: [], env_vars: [], preopened_dirs: [] }
2022-07-01T03:13:21 TRACE wasmtime instance loaded duration_μs=122273
2022-07-01T03:13:21 DEBUG wasmtime initialize duration_μs=122469
2022-07-01T03:13:21 DEBUG Input 'my_input' for argument 'input' is not valid JSON. Wrapping it with quotes to make it a valid string value.
2022-07-01T03:13:21 TRACE wasm invoke target=wafl://__local__.coll/greet
2022-07-01T03:13:21 DEBUG wasm invoke component="greet" id=213710532 payload={"input": [168, 109, 121, 95, 105, 110, 112, 117, 116]}
2022-07-01T03:13:21 TRACE wapc callback
2022-07-01T03:13:21 TRACE wapc callback command=0 arg1=output arg2=1 len=29
2022-07-01T03:13:21 TRACE output payload id=213710532 port="output" payload=[129, 162, 118, 49, 129, 161, 48, 129, 161, 49, 174, 72, 101, 108, 108, 111, 32, 109, 121, 95, 105, 110, 112, 117, 116]
2022-07-01T03:13:21 TRACE deserialized packet id=213710532 port="output" packet=V1(Success(Struct(String("Hello my_input"))))
2022-07-01T03:13:21 TRACE wapc callback done command=0 arg1=output arg2=1 duration_μs=169
2022-07-01T03:13:21 TRACE wapc callback
2022-07-01T03:13:21 TRACE wapc callback command=0 arg1=output arg2=1 len=13
2022-07-01T03:13:21 TRACE output payload id=213710532 port="output" payload=[129, 162, 118, 49, 129, 161, 50, 161, 48]
2022-07-01T03:13:21 TRACE deserialized packet id=213710532 port="output" packet=V1(Signal(Done))
2022-07-01T03:13:21 TRACE wapc callback done command=0 arg1=output arg2=1 duration_μs=111
2022-07-01T03:13:21 TRACE wasm call finished component="greet" id=213710532 duration_μs=21256
2022-07-01T03:13:21 TRACE wasm call result component="greet" id=213710532 result=Ok([])
2022-07-01T03:13:21 TRACE output message {"output":{"value":"Hello my_input"}}
{"output":{"value":"Hello my_input"}}
2022-07-01T03:13:21 TRACE output message {"output":{"signal":"Done","value":null}}
2022-07-01T03:13:21 TRACE stream complete

2.4.2 - Running your WebAssembly component as a microservice

Use wasmflow to turn any WebAssembly module into a standalone microservice.

To turn any WebAssembly module or manifest into a microservice, change the command you use with wasmflow from invoke to serve and pass the --rpc flag to enable the GRPC service. You can supply a custom port with --rpc-port

If you’re following the guide, you can use your own WebAssembly module in place of reg.candle.run/candle/getting-started

$ wasmflow serve reg.candle.run/candle/getting-started --rpc --rpc-port 8060
2022-06-20T22:16:51  INFO GRPC server bound to 127.0.0.1 on port 8060
2022-06-20T22:16:51  INFO Waiting for ctrl-C
2022-06-20T22:16:51  INFO Starting RPC server
Tip

Running wasmflow serve without providing a --port argument will cause wasmflow to automatically select a random, unused port.

Testing your microservice with wafl

wafl is the Wasmflow utility binary that can speak the Wasmflow RPC protocol (among other things).

Using a different terminal and the port from above, run wafl rpc invoke to make the same request we made in the last step.

$ wafl rpc invoke --port=8060 concatenate -- --left=Hello --right=World
{"output":{"value":"Hello World"}}

Congratulations, you just got a command line tool and a streaming GRPC microservice in one!

Next we’re going to connect some components and see how we can start building large projects out of small ones. Next: Flows

2.5 - How to Make New Components Out of Existing Components.

Start connecting simple components on the command line.

The command line is a pipeline of streaming data where every component feeds directly into one other. To Wasmflow it’s like a flow that never branches. It’s a great way to test and experiment with small bits of logic.

The following command pipes one execution of vow into another, both running the same wasm but pointing to different components.

$ wasmflow invoke ./build/my_project.signed.wasm concatenate -- --left=Jane --right=Doe |\
 wasmflow invoke ./build/my_project.signed.wasm greet
{"output":{"value":"Hello Jane Doe"}}

wasmflow output is directly pipable to another execution of wasmflow! You can string together any number of connections to experiment, test, or build on the command line.

Tip

wasmflow connects output from a named port to an incoming port of the same name with one exception shown above. Data coming out of a port named output will be mapped to a port named input on the downstream component. This is a common practice and makes CLI testing more intuitive. Turn it off by passing the --raw flag to wasmflow.

Connecting to remote components

Every tool in the Wasmflow suite talks the same language so switching from one to another should be seamless. This means that you can pipe from wasmflow running WebAssembly locally to wafl which connects to any remote provider and then back to wasmflow again.

Start serving a WebAssembly module with wasmflow to use it as a sample microservice.

$ wasmflow serve ./build/my_project.signed.wasm --rpc --rpc-port 8060
2022-06-20T22:21:10  INFO GRPC server bound to 127.0.0.1 on port 8060
2022-06-20T22:21:10  INFO Starting RPC server
2022-06-20T22:21:10  INFO Waiting for ctrl-C

Then run the following command.

$ wasmflow invoke ./build/my_project.signed.wasm concatenate -- --left=Jane --right=Doe |\
 wafl rpc invoke --port 8060 greet
{"output":{"value":"Hello Jane Doe"}}

In the next step we will publish our artifact to a remote registry so we can access it anywhere.

2.5.1 - Experimenting on the Command Line

Start connecting simple components on the command line.

The command line is a pipeline of streaming data where every component feeds directly into one other. To Wasmflow it’s like a flow that never branches. It’s a great way to test and experiment with small bits of logic.

The following command pipes one execution of wasmflow into another, both running the same .wasm file but pointing to different components.

$ wasmflow invoke reg.candle.run/candle/getting-started concatenate -- --left=Jane --right=Doe |\
 wasmflow invoke reg.candle.run/candle/getting-started greet
{"output":{"value":"Hello Jane Doe"}}

wasmflow output is directly pipable to another execution of wasmflow! You can string together any number of connections to experiment, test, or build on the command line.

Tip

wasmflow connects output from a named port to an incoming port of the same name with one exception shown above. Data coming out of a port named output will be mapped to a port named input on the downstream component.

This is a common practice and makes CLI testing more intuitive. Turn it off by passing the --raw flag to wasmflow.

Connecting to remote components

Every tool in the Wasmflow suite talks the same language so switching from one to another should be seamless. This means that you can pipe from wasmflow running WebAssembly locally to wafl which connects to any remote provider and then back to wasmflow again.

Start serving a WebAssembly module with wasmflow to use it as a sample microservice.

$ wasmflow serve reg.candle.run/candle/getting-started --rpc --rpc-port 8060
2022-06-20T22:21:10  INFO GRPC server bound to 127.0.0.1 on port 8060
2022-06-20T22:21:10  INFO Starting RPC server
2022-06-20T22:21:10  INFO Waiting for ctrl-C

Then run the following command in another terminal.

$ wasmflow invoke reg.candle.run/candle/getting-started concatenate -- --left=Jane --right=Doe |\
 wafl rpc invoke --port 8060 greet
{"output":{"value":"Hello Jane Doe"}}

From here, let’s move on to more complex connections with Flows.

2.5.2 - How to Wire Existing Components Together

Bringing it all together

We’ve done a lot so far without ever touching the core of what’s inside Wasmflow. Wasmflow is all about stitching together disparate logic into one application. We do that in flows. Flows define how components and their ports connect together. It’s a little like a network client connecting to a network port, except it’s code connecting to other code.

Create a new file called salutations.yaml in your project root. You can put it anywhere and name it anything, just make sure you change the examples appropriately.

The current version of the manifest is version 1. The format will change over time and the version will keep things working.

---
# yaml-language-server: $schema=https://wasmflow.com/schema.json
version: 1

First we pull in our external dependencies. Each key here is the namespace to use for the dependency.

---
# yaml-language-server: $schema=https://wasmflow.com/schema.json
---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
Note

As the name suggests, Wasmflow is WebAssembly-focused but that doesn’t mean your external collections of components must be WASM. They can be external microservices, native binaries, workers over a message queue, or other manifests. The default assumes WASM. To specify a different collection type, use the extended form:

---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
  my_microservice:
    kind: GrpcUrl
    url: 127.0.0.1:8080

Next, we define our flow-based components:

# yaml-language-server: $schema=https://wasmflow.com/schema.json
---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
components:
  hello:

The key "hello" here is the name of our flow-based component.

Next we need to define the collections this component has access to and the instances of a collection’s components that we’ll be using in our flow.

# yaml-language-server: $schema=https://wasmflow.com/schema.json
---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
components:
  hello:
    collections:
      - getting_started
    instances:
      greet: getting_started::greet
      concatenate: getting_started::concatenate
Note

Instances are like pointers to a component. Multiple instances can point to the same component.

Now we’re ready to wire everything up.

# yaml-language-server: $schema=https://wasmflow.com/schema.json
---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
components:
  hello:
    collections:
      - getting_started
    instances:
      greet: getting_started::greet
      concatenate: getting_started::concatenate
    flow:
      - <>.first_name -> concatenate.left
      - <>.last_name -> concatenate.right
      - concatenate.output -> greet.input
      - greet.output -> <>

The connections in a flow describe how an instance of a component connects to instances of other components. It works similarly to how you pipe commands together on the command line, except the connections are embedded in configuration and can have multiple ins and outs.

Tip

The connections above are written in short form syntax which is a light DSL that simplifies writing connections by hand. See short form syntax documentation for more details.

Now run your schematic directly with wasmflow invoke.

$ wasmflow invoke salutations.yaml hello -- --first_name=Jane --last_name=" Doe"
{"output":{"value":"Hello Jane  Doe"}}

Just as with a wasm file, start a GRPC microservice or HTTP server with wasmflow serve:

$ wasmflow serve salutations.yaml --rpc --rpc-port 8060
2021-11-16T14:19:39  INFO Starting RPC server
2021-11-16T14:19:39  INFO Host started
2021-11-16T14:19:39  INFO GRPC server bound to 127.0.0.1 on port 8060
2021-11-16T14:19:39  INFO Waiting for Ctrl-C

And use wafl the same way we already have.

$ wafl rpc invoke --port=8060 hello -- --first_name=Jane --last_name=Doe
{"output":{"value":"Hello Jane Doe"}}

We just turned a yaml file into a collection of new components with the same capabilities as compiled code. Next: dive into the composability you just unlocked.

2.5.3 - Composability in Wasmflow

Coming full circle

Wasmflow was built for the sole reason of making software more reusable. Ultimately, that means making code connect with other code more easily. In the Flow section we learn how manifests connect code to other code to provide new capabilities. This section keeps adding to that concept by having manifests depend on other manifests.

Save the following in a file called host.yaml.

# yaml-language-server: $schema=https://wasmflow.com/schema.json
---
version: 1
external:
  getting_started: reg.candle.run/candle/getting-started
  salutations:
    kind: Manifest
    reference: ./salutations.yaml
components:
  good_day:
    collections:
      - getting_started
      - salutations
    instances:
      hello: salutations::hello
      concatenate: getting_started::concatenate
      message:
        id: core::sender
        config:
          output: ", have a great day"
    flow:
      - <> -> hello.first_name
      - <> -> hello.last_name
      - hello.output -> concatenate.left
      - message.output -> concatenate.right
      - concatenate.output -> <>
Tip

message:
  id: core::sender
  config:
    output: ", have a great day"

This new instance is one of the core Wasmflow components. It sends a value out of its output port.

Run your new manifest via wasmflow just as you did the last.

$ wasmflow invoke host.yaml good_day -- --first_name=Jane --last_name="Doe"
{"output":{"value":"Hello Jane Doe, have a great day"}}

Now we’ve extended functionality simply by connecting the same modules we’ve already written without writing new code or rebuilding anything.

Collections, components, and flows are the core pieces for connecting disparate software via the same component/port interface we’ve been using in this guide. Next: Publishing components

2.6 - Publishing components

Store and run signed artifacts from an OCI registry.

Wasmflow tools will automatically pull from an OCI registry if the passed filename isn’t found on the local filesystem. If you don’t have an OCI registry handy, you can start one easily with Docker.

$ docker run -it --rm -p 5000:5000 registry
Tip

This will spin up a local registry with default settings. It’s suitable for testing but production registries will need more configuration.

Publish your artifact

The wafl registry push command will publish WebAssembly, manifests, or multi-architecture binaries to OCI registries.

If you’ve built the sample component in this guide, use this command to push it to test/getting-started:latest on our local registry.

$ wafl registry push 127.0.0.1:5000/test/getting-started:latest build/my_project.signed.wasm --insecure 127.0.0.1:5000
2022-06-20T22:23:50  INFO Pushing artifact...
Manifest URL: http://127.0.0.1:5000/v2/test/getting-started/manifests/sha256:d9117f3cd306b4f82179923d7cfd1283fb37e3ba3fa5b1faeac84964b208749d
Config URL: http://127.0.0.1:5000/v2/test/getting-started/blobs/sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a

Fetch your remote components

To run your components remotely all you do is pass the registry URL to vow in place of the filename we’ve used previously. wasmflow will take care of fetching and caching the remote artifact.

$ wasmflow invoke 127.0.0.1:5000/test/getting-started:latest greet --latest --insecure 127.0.0.1:5000 -- --input="Jane Doe"
{"output":{"value":"Hello Jane Doe"}}

2.7 - Contributing to Wasmflow development

How to contribute to the Wasmflow project.

Visit wasmflow on github to clone the source code.

Wasmflow is a public project that accepts Pull Requests and Bug Reports. File a bug report here

Wasmflow’s README will always have the latest documentation on building, testing, and running the project.

3 - Tools

Meet the Wasmflow tools.

3.1 - Wafl

The Wasmflow utility binary.

wafl (lovingly pronounced “waffle”) is the Wasmflow utility binary. It is an RPC client, project bootstrapper, and registry manipulator (among other things).

You don’t need wafl to run wasmflow. It can be distributed or updated on its own and omitted from deployments.

USAGE

wafl <COMMAND> <SUBCOMMAND>

Subcommands

rpc

The rpc subcommands connect to a remote wasmflow host and issue an RPC request.

  • rpc invoke: Invoke a component on the remote host.
  • rpc list: Retrieve the list of exposed components on the remote host.
  • rpc stats: Get the running statistics of the remote host.

project

The project subcommands houses functions that help you build, deploy, and maintain applications.

  • project new: Create a new Wasmflow project.

component

The component subcommands help you build new components in an existing project.

  • component new: Create a new schema for a Wasmflow component.

registry

registry subcommands are for interacting with an OCI registry.

  • registry push: Push an image to an OCI registry.
  • registry pull: Pull an image from an OCI registry.

wasm

Wafl’s wasm subcommands help you sign, inspect, and manage WasmFlow WebAssembly artifacts.

  • wasm sign: Sign a .wasm file with local keys.
  • wasm inspect: Inspect the embedded details of a signed Wasmflow .wasm file.

bundle

The bundle subcommands help you manage native, multi-architecture binary collections.

  • bundle pack: Create a signed archive bundle.

help

Prints the help message with more details on commands and arguments.

3.2 - Wasmflow

The Wasmflow host.

wasmflow is the runtime executor for Wasmflow manifests and WebAssembly collections.

USAGE

wasmflow <SUBCOMMAND>

Subcommands

serve

Start a persistent host from a manifest or .wasm module, optionally exposing an RPC microservice or connecting to a mesh of hosts across a message queue.

invoke

Invoke a component exposed in a manifest or a .wasm module.

list

Print the components exposed by a manifest or a .wasm module.

test

Load a manifest or .wasm module and run automated unit tests against its components.

help

Prints this message or the help of the given subcommand(s)

4 - Configuration

Manifests and other configurable parts of the Wasmflow toolchain

4.1 - Short Form Syntax

Alternative syntax for connections and connection targets

Writing out full ConnectionDefinition structures in can be repetitive. An alternative is to specify either the entire ConnectionDefinition or individual ConnectionTargetDefinitions in short form syntax.

Common Example

Verbose form

- from:
    instance: instance1
    port: instance1_output
  to:
    instance: instance2
    port: instance2_input

Alternative short form

- instance.instance -> instance2.instance2

Schematic Input/Output example

A common practice is to mirror the port name of the connected ports as the name for the attached schematic input and output. The diamond, <> is short form for <input> when in the from position and <output> in the to position with a port name of the connected port.

Verbose form

- from:
    instance: <input>   // <input> refers to the schematic input
    port: instance1_input
  to:
    instance: instance1
    port: instance1_input
- from:
    instance: instance1
    port: instance1_output
  to:
    instance: <output>
    port: instance1_output

Alternative short form

- <> -> instance1.instance1
- instance1.instance1 -> <>

Schematic Input/Output with explicit port name

Verbose form

- from:
    instance: <input>
    port: schematic_input
  to:
    instance: instance1
    port: instance1_input

Alternative short form

- <>[schematic_input] -> instance1.instance1

Specifying default input

It’s common to specify default inputs to ports and this can be done by omitting the from reference and providing a default value. Defaults are parsed as JSON.

Verbose form

- to:
    instance: instance1
    port: instance1_input
  default: '"Default input"'

Alternative short form

- '"Default input" -> instance1.instance1'

Specifying single ConnectionTarget

The short form for a [ConnectionTargetDefinition][] is valid on either the from or to fields individually.

Verbose form

- from:
    instance: instance1
    port: instance1_output
  to:
    instance: instance2
    port: instance2_input

Alternative short form

- from:
    instance: instance1
    port: instance1_output
  to: instance2.instance2

5 - Search Results