Skip to content

Kv

Reactive values addressed by path, with no struct declared. For a key set that is not known at compile time, or where a schema is more ceremony than the job is worth.

let kv = store.kv();
kv.set("theme", &"dark".to_string())?;
let theme = kv.get::<String>("theme")?;
let ui = kv.namespace("ui");
ui.set("width", &800u32)?;
let under_ui = ui.keys()?;
kv.remove("theme")?;

Every name here is a single level, and namespace is what nests. So kv.set("ui.width", ..) writes one key called ui.width - a name that happens to hold a dot - while kv.namespace("ui").set("width", ..) writes width inside ui.

The dot in a name is escaped on disk, so the two are different places and stay different. Getting this the wrong way round is the mistake to watch for: a dotted string looks like a path and is not one.

keys() lists everything under the handle it is called on, at every level below it and sorted. Narrowing a listing is what namespace is for; the root handle lists the whole store.

kv.set("theme", &"dark".to_string())?;
kv.namespace("ui").set("width", &800u32)?;
kv.namespace("ui").namespace("panel").set("left", &true)?;
let from_ui: Vec<String> = kv
.namespace("ui")
.keys()?
.iter()
.map(|key| key.to_string())
.collect();
assert_eq!(from_ui, ["ui.panel.left", "ui.width"]);

The paths that come back are whole, so a width inside ui is listed as ui.width, and theme at the root is not listed at all from a ui handle.

let width = kv.namespace("ui").cell("width", 800u32)?;
let flags = kv.map::<String, bool>("flags")?;
flags.insert("dark".to_string(), &true)?;

What comes back is an ordinary ReactiveCell or ReactiveMap, so subscriptions work as they do for declared fields. The type of a cell comes from its default, so there is nothing to spell out twice.

A declared prefix belongs to that struct, and writing inside it through Kv is refused:

let said = match kv.namespace("network").set("port", &"8080".to_string()) {
Err(KvWrite::Declared {
at,
declared_at,
by,
}) => {
format!("{at} lies in {declared_at}, which {by} declared")
}
other => {
other?;
"the write went through".to_string()
}
};
kv.namespace("networkish")
.set("port", &"8080".to_string())?;

Printed as it stands, the refusal is one line:

network.port is declared by Network

The three facts are on the variant. at is the path that was written, declared_at the path the schema declared - at itself, or one it lies inside

  • and by the struct that declared it:
network.port lies in network.port, which Network declared

networkish is untouched by any of it: what a declaration owns is its own path and what lies inside it, not every name that starts the same way.

A cell or a map over the same place is an open rather than a write, so that one comes back as OpenStruct::Taken - the same collision said by the set the call belongs to.

The reason is what a wrong type does to the struct. Storing a String where a u16 is declared leaves the field’s subscription unable to decode it, so the field goes on showing its old value while the store holds something else - and the next startup fails outright reading that path back.

let ui = kv.namespace("ui");
let _width = ui.cell("width", 800u32)?;
let refused = ui.cell("width", String::new());

Nothing here records a type. A cell reads its path to seed itself, so the second ask finds what the first stored and fails to decode it. The error is a codec error naming what it found.

The check is therefore the stored value, and that has consequences a registry would not. It holds across restarts, because what an earlier run wrote is still there to be read. It holds over a path that was empty, because the first cell writes its default there. And it does not hold for raw access at all:

kv.set("thing", &1u32)?;
kv.set("thing", &"now a string".to_string())?; // replaces it
kv.get::<u32>("thing")?; // Err, at the read

A raw set overwrites whatever was there, whatever its type. The disagreement surfaces at the next get that asks for the old one.

On redb and sqlite there is nothing to say: every key is stored whole, and a Kv write is one more of them.

A document engine - json, toml, ron - holds two things, and this is where you see it. A declared struct is written as a tree, level by level, because the declarations say where each of its values ends. A path nothing declares is written whole, as one key beside those trees:

store
.kv()
.namespace("plugins")
.namespace("left")
.set("width", &240u32)?;
{
"chrome": {
"width": 800
},
"plugins.left.width": 240
}