aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-30 19:13:40 +0100
committertv <tv@krebsco.de>2026-10-30 19:13:40 +0100
commit289b84b09a791b88f039664d71bb644c94de6000 (patch)
tree727ee46dc62409f2b5ca2c31def506034e4f92be /src/document
parent7d3d8031d2301f24b32ee08215a8d004fb81493b (diff)
api: add support for shallow store reads
Diffstat (limited to 'src/document')
-rw-r--r--src/document/store.rs72
1 files changed, 72 insertions, 0 deletions
diff --git a/src/document/store.rs b/src/document/store.rs
index e742edb..89058fb 100644
--- a/src/document/store.rs
+++ b/src/document/store.rs
@@ -234,6 +234,78 @@ pub(crate) fn read_path_traced(
Ok((value, !log.reports.is_empty()))
}
+/// Strings and bytes up to this long are in a shallow read's summaries.
+const SHALLOW_INLINE: usize = 256;
+
+/// What's at `path` (the empty path is the whole store), one level deep: a summary of
+/// it, `type` (`map`, `list`, `text`, `string`, `bytes`, `number`, `boolean`, `null`,
+/// or `unknown`, an Automerge scalar of a later version)
+/// and `size` (keys, items, characters or bytes), and its `value` where it's a short
+/// scalar; a map's has its keys' summaries too, under `entries`. Concurrent values
+/// resolved as a read resolves them; conflicts not reported.
+pub(crate) fn read_shallow(
+ doc: &AutoCommit,
+ path: &[String],
+) -> Result<Option<JsonValue>, StoreError> {
+ let Some(resolved) = resolve_at(doc, path)? else {
+ return Ok(None);
+ };
+ summary(doc, &resolved, true).map(Some)
+}
+
+/// A summary of `resolved` (`read_shallow`); with `entries`, a map's keys' too.
+fn summary(doc: &AutoCommit, resolved: &Resolved, entries: bool) -> Result<JsonValue, StoreError> {
+ let mut out = serde_json::Map::new();
+ match resolved {
+ Resolved::Maps(maps) => {
+ let keys = merged_keys(doc, maps);
+ out.insert("type".into(), "map".into());
+ out.insert("size".into(), keys.len().into());
+ if entries {
+ let mut listed = serde_json::Map::new();
+ for key in keys {
+ if let Some(child) = resolve(doc, maps, &key, None)? {
+ listed.insert(key, summary(doc, &child, false)?);
+ }
+ }
+ out.insert("entries".into(), JsonValue::Object(listed));
+ }
+ }
+ Resolved::Other(Value::Object(ObjType::Map), id) => {
+ return summary(doc, &Resolved::Maps(vec![id.clone()]), entries);
+ }
+ Resolved::Other(Value::Object(ObjType::List), id) => {
+ out.insert("type".into(), "list".into());
+ out.insert("size".into(), doc.length(id).into());
+ }
+ Resolved::Other(Value::Object(ObjType::Text), id) => {
+ out.insert("type".into(), "text".into());
+ out.insert("size".into(), doc.length(id).into());
+ }
+ Resolved::Other(Value::Object(ObjType::Table), _) => {
+ return Err(anyhow!("Automerge tables are not supported").into());
+ }
+ Resolved::Other(Value::Scalar(scalar), _) => {
+ let (kind, size) = match &**scalar {
+ ScalarValue::Str(text) => ("string", Some(text.len())),
+ ScalarValue::Bytes(bytes) => ("bytes", Some(bytes.len())),
+ ScalarValue::Null => ("null", None),
+ ScalarValue::Boolean(_) => ("boolean", None),
+ ScalarValue::Unknown { .. } => ("unknown", None),
+ _ => ("number", None),
+ };
+ out.insert("type".into(), kind.into());
+ if let Some(size) = size {
+ out.insert("size".into(), size.into());
+ }
+ if kind != "unknown" && size.is_none_or(|size| size <= SHALLOW_INLINE) {
+ out.insert("value".into(), scalar_to_json(scalar.clone().into_owned())?);
+ }
+ }
+ }
+ Ok(JsonValue::Object(out))
+}
+
/// Keys of the merged map at `path`; empty if it doesn't exist or isn't a map.
pub(crate) fn keys_at(doc: &AutoCommit, path: &[String]) -> Result<Vec<String>, StoreError> {
let mut maps = vec![automerge::ROOT];