Skip to content

Provider SDK

A provider is a wasm32-wasip2 component that teaches omnifs how one system appears as paths. The host owns FUSE, credentials, cache, and callouts. Your provider owns routes, object identity, and rendering.

A provider has:

  • a config type,
  • a state type,
  • a #[omnifs_sdk::provider] impl,
  • route registrations in start,
  • handlers for files, directories, tree refs, or object leaves,
  • an omnifs.provider.json manifest.

Workspace providers use one top-level provider impl:

use omnifs_sdk::prelude::*;
struct DbProvider;
#[omnifs_sdk::provider(metadata = "omnifs.provider.json")]
impl DbProvider {
type Config = Config;
type State = State;
fn start(config: Config, r: &mut Router<State>) -> Result<State> {
let state = State::open(config)?;
r.dir("/tables").handler(tables_list)?;
r.file("/tables/{table}/schema.sql").handler(table_schema_sql)?;
Ok(state)
}
}

There is no #[dir] or #[file] route attribute. Routes are registered imperatively.

Use #[omnifs_sdk::config] for the Rust config type. The macro wires deserialization for provider startup. The public schema is still authored in omnifs.provider.json.

#[omnifs_sdk::config]
struct Config {
path: String,
sample_limit: Option<u32>,
}

Do not rely on Rust config structs to generate the manifest schema. Keep the manifest explicit.

Use the router methods that exist in the current SDK:

MethodUse
r.dir(path).handler(handler)directory listing and lookup behavior
r.file(path).handler(handler)file bytes
r.treeref(path).handler(handler)hand off a real backing tree, such as a cloned repo
`r.object::(path,o
`r.file_object::(path,o
r.attach(path, &subtree)attach a detached object subtree

Variable segments use {name} captures. Capture field types parse with FromStr; malformed captures remove that route from candidacy.

Object-shaped providers declare canonical resources once and render leaves from them. A fresh load can store canonical upstream bytes and materialized view leaves. A warm read can render from host-pushed canonical bytes.

The route block is only half of an object route:

r.object::<TableDoc>("/tables/{table}", |o| {
o.representations("table", ())?;
o.file("schema.sql").handler(table_schema_sql)?;
o.file("schema.json").handler(table_schema_json)?;
o.file("count.txt").handler(table_count_txt)?;
o.file("sample.json").handler(table_sample)?;
Ok(())
})?;

The object type still needs an object declaration, a typed key, and a Key::load implementation. That load function is where the provider returns Fresh, Unchanged, or NotFound and supplies canonical bytes for the object cache.

Use object routes when several files describe the same upstream resource. Use plain file routes for independent leaves.

The provider macro can declare compile-time resources used by typed endpoints and git handoffs:

#[omnifs_sdk::provider(
metadata = "omnifs.provider.json",
resources(endpoints = [api::GitHubApi], git = true)
)]
impl GitHubProvider {
/* ... */
}

The manifest declares package metadata, default mount name, capabilities, auth, and config schema.

{
"id": "example",
"displayName": "Example",
"provider": "omnifs_provider_example.wasm",
"defaultMount": "example",
"capabilities": [
{
"kind": "domain",
"value": "api.example.com",
"why": "Fetch Example API resources."
},
{
"kind": "memoryMb",
"value": 64,
"why": "Declare the provider's expected memory need."
}
]
}

Add auth only when the provider needs credentials. Add a preopened path only when the provider needs a local file capability.

During SDK work, validate the provider as code and as a filesystem surface:

Terminal window
just providers-check
cargo check -p omnifs-provider-db --target wasm32-wasip2
cargo test -p omnifs-provider-db --target wasm32-wasip2 --no-run
omnifs dev -y
omnifs shell

Inside the shell, test with ls, cat, jq, find, and grep. A provider is not done when the handler compiles. It is done when normal tools can traverse the path surface without special knowledge.