Getting Started
This guide will help set up a simple MAF application (both server and client) that synchronizes a counter between multiple clients.
⚠️ NOTE: MAF is still in very early development: things are not easy to do, APIs and features are subject to change, and things will break!
MAF is a full-stack framework, helping you build both server and client code. This guide guides you through setting up a project with both server and client code, so it is recommended to use the following file structure:
project-root/
|-- server/ # Your server code
|-- ... # ... other server files (with your code)
|-- client/ # Your client code
|-- ... # ... other client files (with your code)
Sometimes, you may want to use a different file structure, such as having pre-made server code and you want to add client code to it, or vice versa. Decide what works best for you, but keep in mind that the guide will assume the above structure.
MAF CLI is a command-line tool that helps you run, test, and deploy MAF servers.
You will need to have Git and a Rust toolchain installed to build MAF CLI from Scratch. See https://www.rust-lang.org/tools/install for Rust installation instructions.
To install MAF CLI:
$ cargo install --git https://github.com/giilbert/maf.git maf_cli
If the installation is successful, you should be able to run the maf_cli command:
$ maf_cli -V
maf_cli 0.1.0
Add the following to your shell configuration file (e.g., ~/.bashrc, ~/.zshrc):
alias maf="maf_cli"
This will allow you to run the maf command instead of maf_cli. The rest of the documentation will refer to the maf command.
1. Initialize a new MAF project:
$ maf create --template rust
? Server path (Defaults to './server'):
? Name (Lowercase alphanumeric characters and hyphens): enter-project-name
INFO: Setting up project 'enter-project-name' using template 'rust' in ...
2. src/lib.rs should include the following code:
use maf::*;
struct CounterStore {
count: i32,
}
impl StoreData for CounterStore {
type Data<'this> = i32;
// ^ This lifetime is used for borrowing data from the store
// when selecting data to send to the client. (Read more below!)
fn init() -> Self {
Self { count: 0 }
}
// Determines what data to send to the client when the store is serialized
// v Used when borrowing data
fn select(&self, _user: &User) -> Self::Select<'_> {
self.count
}
// This name will be used to identify the store
fn name() -> impl AsRef<str> {
"counter"
}
}
// RPC functions can be used to perform actions on the server
async fn increment_counter(
// Special types for extracting parameters, data, and context
Params(counter): Params<i32>,
test: Store<CounterStore>
) -> i32 {
let mut data = test.write().await;
*data += counter;
println!("incremented counter by {counter}. new value: {}", &*data);
*data
}
async fn on_connect(user: User) {
println!("user connected! id: {}", user.meta.id());
}
// Declare what the MAF application should do
fn build() -> App {
App::builder()
.on_connect(on_connect)
.rpc("increment_counter", increment_counter)
.build()
}
maf::register!(build);
3. Build and run the server with:
$ maf run
If all goes well, you should see the following:
INFO: running build command `cargo build --target wasm32-wasip2` in ...
----------
[cargo build output]
-----------
INFO: build completed in 12.34s
INFO: loaded room from <output path>
INFO: dev server listening on 1147
❗ The development server will listen on port 1147 by default. You can change this by passing the --port flag to the maf run command.
The client library allows you to connect to a MAF server and interact with it.
1. Install the @usemaf/client package.
2. Install additional dependencies to build/run your code. If you're building a website, you will need a bundler like Vite, Parcel, or Webpack.
3. Write the following code:
import { MafClient } from "@usemaf/client";
const client = new MafClient({
// NOTE: Change this when you deploy your server
server: "dev",
});
// Use the same name as in `StoreData`
const store = client.store("counter");
store.on("change", (data) => {
console.log("Counter value changed:", data);
});
async function run() {
await client.connect();
console.log("Connected to the server!");
// Call the RPC function to increment the counter every second
while (true) {
const _result = await client.rpc<number>("increment_counter", 1);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}
run();