///|
/// One remembered value: a space, a key inside it, and the value itself.
pub(all) struct Item {
space : Array[String]
key : String
value : Json
}
///|
/// Long-term memory that outlives a thread: spaced key-value pairs with a
/// search over them. A node reaches the store through `Run::store`.
pub(open) trait Store {
///|
async fn get(Self, space : Array[String], key : String) -> Item?
///|
async fn put(Self, space : Array[String], key : String, value : Json) -> Unit
///|
async fn delete(Self, space : Array[String], key : String) -> Unit
///|
/// Items under `space` (or below it), newest first. `query` is matched
/// against the item's text, `filter` against its top-level object fields.
async fn search(
Self,
space : Array[String],
query? : String,
filter? : Map[String, Json],
limit? : Int32,
) -> Array[Item]
}
///|
/// A store that keeps everything in memory, for tests and for short-lived
/// processes.
pub struct MemoryStore {
items : Map[String, Item]
}
///|
pub fn MemoryStore::new() -> MemoryStore {
{ items: Map([]), }
}
///|
fn item_key(space : Array[String], key : String) -> String {
let path = StringBuilder()
for part in space {
path.write_string(part)
path.write_char('\u{1f}')
}
path.write_char('\u{1e}')
path.write_string(key)
path.to_string()
}
///|
fn under_space(item : Item, space : Array[String]) -> Bool {
guard item.space.length() >= space.length() else { return false }
for i = 0; i < space.length(); i = i + 1 {
if item.space[i] != space[i] {
return false
}
}
true
}
///|
fn matches_filter(item : Item, filter : Map[String, Json]) -> Bool {
guard item.value is Object(fields) else { return filter.is_empty() }
for name, wanted in filter {
match fields.get(name) {
Some(actual) => if actual != wanted { return false }
None => return false
}
}
true
}
///|
pub impl Store for MemoryStore with fn get(self, space, key) -> Item? raise {
self.items.get(item_key(space, key))
}
///|
pub impl Store for MemoryStore with fn put(self, space, key, value) -> Unit raise {
self.items[item_key(space, key)] = { space, key, value, }
}
///|
pub impl Store for MemoryStore with fn delete(self, space, key) -> Unit raise {
self.items.remove(item_key(space, key))
}
///|
pub impl Store for MemoryStore with fn search(
self,
space,
query?,
filter?,
limit?,
) -> Array[Item] raise {
let wanted = match limit {
Some(limit) => limit
None => 10
}
let found = []
for _, item in self.items {
if !under_space(item, space) {
continue
}
match query {
Some(text) => if !item.value.stringify().contains(text) { continue }
None => ()
}
match filter {
Some(filter) => if !matches_filter(item, filter) { continue }
None => ()
}
found.push(item)
}
if found.length() > wanted {
let cut = []
for index = 0; index < wanted; index = index + 1 {
cut.push(found[index])
}
cut
} else {
found
}
}
///|
pub extend MemoryStore with Store::{get, put, delete, search}