pub struct DataDoc { /* private fields */ }Expand description
A replicated document.
Holds named collections (map, list, text, counter). Concurrent
edits from any replica merge deterministically, and applying the same
change twice is a no-op, which is what lets this layer ride a transport
ladder that only promises at-least-once, unordered delivery.
Implementations§
Source§impl DataDoc
impl DataDoc
Sourcepub fn new() -> Self
pub fn new() -> Self
Create an empty document.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn is_poisoned(&self) -> bool
pub fn is_poisoned(&self) -> bool
Whether this handle has been poisoned by a decode failure.
A poisoned handle never recovers. The owner drops it and re-opens the document from storage, which is a decision only the owner can make.
Sourcepub fn import(&mut self, bytes: &[u8]) -> DataResult<ImportOutcome>
pub fn import(&mut self, bytes: &[u8]) -> DataResult<ImportOutcome>
Apply an encoded snapshot or delta.
Every blob reaching this method comes back out of a sealed
protocol-state record, so an AEAD tag has already established that
these are bytes this SDK wrote and nobody altered. That is the real
containment for the engine’s known import-panic classes, and the
reason no code path may ever feed unverified bytes here: the
catch_unwind below is a second line, which poisons this handle
when it catches, and under panic = "abort" it does not exist at
all. It wraps the engine’s apply step only. The header decode just
above it, like DataDoc::inspect, runs outside it, so a panic
there unwinds to the caller and leaves this handle live.
Returns whether the blob applied or is parked behind a missing
predecessor. Callers that can destroy history (compaction, log
truncation) must branch on it: see ImportOutcome.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn inspect(&self, bytes: &[u8]) -> DataResult<BlobMeta>
pub fn inspect(&self, bytes: &[u8]) -> DataResult<BlobMeta>
Judge a blob against this document without letting the engine touch it.
The whole point is that this runs first. Header decoding with the checksum on is the cheap part; the verdicts it produces are what let a caller refuse the two blob shapes that are known to end a process rather than return an error.
Sourcepub fn import_remote(&mut self, bytes: &[u8]) -> DataResult<RemoteImport>
pub fn import_remote(&mut self, bytes: &[u8]) -> DataResult<RemoteImport>
Apply a blob that arrived from a peer.
DataDoc::import exists for bytes that came back out of a sealed
record, where an AEAD tag has already established that this SDK wrote
them. Nothing of the sort is true here: a peer is authenticated, which
says who sent the bytes and nothing at all about their shape. So this
path re-judges every blob with DataDoc::inspect and declines the
two shapes the engine is known to panic on, rather than relying on
the unwinding catch in DataDoc::import: it covers the apply step
only, poisons this handle when it fires, and does not exist in a host
built with panic = "abort".
Refusing is not a failure: a duplicate is the ladder keeping its at-least-once promise, and a trimmed-history gap in a run of changes is answered by asking for a snapshot. Both are recorded outcomes, not errors. The same refusal on a snapshot is the exception: it means the replicas have diverged past anything a frame can carry, and the caller has nothing left to ask for.
Sourcepub fn export_since(&self, from: &VersionToken) -> DataResult<CatchUp>
pub fn export_since(&self, from: &VersionToken) -> DataResult<CatchUp>
Encode what a replica at from is missing.
from is a token that replica produced with DataDoc::version.
A token this document cannot decode is treated as corrupt rather than
as “send everything”: on the sync path the token is remote input, and
answering a malformed one with the whole document would make a garbled
byte an amplification lever.
Sourcepub fn mark_persisted(&mut self)
pub fn mark_persisted(&mut self)
Declare everything currently in the document as already persisted.
Called after loading a snapshot and its delta log, so the next commit exports only genuinely new changes instead of re-emitting the history that was just read off disk.
Sourcepub fn commit(&mut self) -> DataResult<Option<Delta>>
pub fn commit(&mut self) -> DataResult<Option<Delta>>
Commit pending edits and return the change they produced.
Ok(None) means nothing had changed. The returned delta is exactly
the bytes needed to bring a replica at the previous version up to the
current one, which is what the delta log stores and what F3 will send.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn rewind_last_commit(&mut self)
pub fn rewind_last_commit(&mut self)
Undo the bookkeeping of the last commit, so its change is handed out again by the next one.
For the caller whose write failed. The document keeps the edits (they happened, and the application is looking at them); what is undone is only the claim that someone has taken responsibility for persisting them. Without this a failed write loses the change at the next restart, silently, which is the worst shape a storage error can take.
Safe to call spuriously: re-exporting a delta that did reach storage costs one duplicate record, and duplicates are free by construction.
Sourcepub fn version(&self) -> VersionToken
pub fn version(&self) -> VersionToken
The current version of the document.
Sourcepub fn export_compacted(&self) -> DataResult<Vec<u8>>
pub fn export_compacted(&self) -> DataResult<Vec<u8>>
Encode the document compactly, discarding history.
This is what a document record stores: a 10,000 operation document compacts to under a kilobyte here, against tens of kilobytes of full history, which is what keeps a document inside one sealed record and a catch-up inside one message.
Sourcepub fn export_raw(&self) -> DataResult<Vec<u8>>
pub fn export_raw(&self) -> DataResult<Vec<u8>>
Encode the document with its full history.
Part of the escape hatch: an application can always take this and
leave. Prefer DataDoc::export_compacted for storage and transfer.
Sourcepub fn export_json(&self) -> DataResult<String>
pub fn export_json(&self) -> DataResult<String>
The document’s current state as plain JSON.
The second half of the escape hatch, and the one that does not require the reader to know anything about this SDK at all.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn measure(&mut self) -> DataResult<usize>
pub fn measure(&mut self) -> DataResult<usize>
Measure the compacted encoding and refresh the size bookkeeping.
Sourcepub fn check_size(&mut self) -> DataResult<(SizeVerdict, Option<usize>)>
pub fn check_size(&mut self) -> DataResult<(SizeVerdict, Option<usize>)>
Check the document against the size cap, measuring only when the running estimate says a breach is possible.
Returns the verdict and, when a measurement happened, the true size.
Sourcepub fn stats(&mut self) -> DataResult<DocStats>
pub fn stats(&mut self) -> DataResult<DocStats>
Statistics for compaction and cap decisions.
Sourcepub fn commits_since_compaction(&self) -> u32
pub fn commits_since_compaction(&self) -> u32
Commits recorded since the last compaction.
Sourcepub fn mark_compacted(&mut self, compacted_bytes: usize)
pub fn mark_compacted(&mut self, compacted_bytes: usize)
Reset the compaction counter, after the owner has written a fresh compacted record and dropped the folded delta records.
Sourcepub fn restore_bookkeeping(
&mut self,
compacted_bytes: usize,
log_bytes: usize,
commits: u32,
)
pub fn restore_bookkeeping( &mut self, compacted_bytes: usize, log_bytes: usize, commits: u32, )
Adopt the bookkeeping of a document just loaded from storage.
Without this a reopened document starts as though it were empty, and the cheap size estimate then under-reports by the entire size of what was loaded: a document already near the cap would look far from it and sail past without ever triggering a real measurement. Passing the record sizes in keeps the estimate an over-estimate, which is the property the whole check depends on.
compacted_bytes is the size of the stored snapshot and log_bytes
the total size of the delta records replayed on top of it.
Sourcepub fn map_set(
&mut self,
collection: &str,
key: &str,
value: DataValue,
) -> DataResult<()>
pub fn map_set( &mut self, collection: &str, key: &str, value: DataValue, ) -> DataResult<()>
Set a key in a map collection.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn map_delete(&mut self, collection: &str, key: &str) -> DataResult<()>
pub fn map_delete(&mut self, collection: &str, key: &str) -> DataResult<()>
Remove a key from a map collection.
Sourcepub fn map_get(
&self,
collection: &str,
key: &str,
) -> DataResult<Option<DataValue>>
pub fn map_get( &self, collection: &str, key: &str, ) -> DataResult<Option<DataValue>>
Read a key from a map collection.
Sourcepub fn list_push(
&mut self,
collection: &str,
value: DataValue,
) -> DataResult<()>
pub fn list_push( &mut self, collection: &str, value: DataValue, ) -> DataResult<()>
Append to a list collection.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn list_insert(
&mut self,
collection: &str,
index: usize,
value: DataValue,
) -> DataResult<()>
pub fn list_insert( &mut self, collection: &str, index: usize, value: DataValue, ) -> DataResult<()>
Insert into a list collection at index.
Sourcepub fn list_delete(
&mut self,
collection: &str,
index: usize,
count: usize,
) -> DataResult<()>
pub fn list_delete( &mut self, collection: &str, index: usize, count: usize, ) -> DataResult<()>
Delete count entries from a list collection at index.
Sourcepub fn list_len(&self, collection: &str) -> DataResult<usize>
pub fn list_len(&self, collection: &str) -> DataResult<usize>
Number of entries in a list collection.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn text_insert(
&mut self,
collection: &str,
position: usize,
text: &str,
) -> DataResult<()>
pub fn text_insert( &mut self, collection: &str, position: usize, text: &str, ) -> DataResult<()>
Insert text into a text collection at a character position.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn text_delete(
&mut self,
collection: &str,
position: usize,
count: usize,
) -> DataResult<()>
pub fn text_delete( &mut self, collection: &str, position: usize, count: usize, ) -> DataResult<()>
Delete count characters from a text collection at position.
Sourcepub fn text_value(&self, collection: &str) -> DataResult<String>
pub fn text_value(&self, collection: &str) -> DataResult<String>
The current contents of a text collection.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn counter_increment(
&mut self,
collection: &str,
amount: f64,
) -> DataResult<()>
pub fn counter_increment( &mut self, collection: &str, amount: f64, ) -> DataResult<()>
Add to a counter collection. Negative values subtract.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Sourcepub fn counter_value(&self, collection: &str) -> DataResult<f64>
pub fn counter_value(&self, collection: &str) -> DataResult<f64>
The current value of a counter collection.
Examples found in repository?
29fn main() -> Result<(), Box<dyn std::error::Error>> {
30 // One shared starting point: Ada writes, and Bo receives that change.
31 let mut ada = DataDoc::new();
32 ada.map_set("meta", "title", text("Coast road"))?;
33 ada.list_push("packing", text("water"))?;
34 let shared = ada.commit()?.expect("a change to share");
35
36 let mut bo = DataDoc::new();
37 bo.import(&shared.bytes)?;
38
39 println!("both start at: {}", bo.export_json()?);
40
41 // The link goes away here. Neither side can see the other's edits, and
42 // neither side blocks on that.
43 ada.map_set("meta", "title", text("Coast road, Friday"))?;
44 ada.list_push("packing", text("map"))?;
45 ada.text_insert("notes", 0, "leave at six")?;
46 ada.counter_increment("edits", 1.0)?;
47 let from_ada = ada.commit()?.expect("Ada's offline work");
48
49 bo.map_set("meta", "title", text("Coast road, Saturday"))?;
50 bo.list_push("packing", text("torch"))?;
51 bo.text_insert("notes", 0, "bring the tent, ")?;
52 bo.counter_increment("edits", 1.0)?;
53 let from_bo = bo.commit()?.expect("Bo's offline work");
54
55 println!("apart, Ada has: {}", ada.export_json()?);
56 println!("apart, Bo has: {}", bo.export_json()?);
57
58 // The link comes back. Each side imports what the other committed. Order
59 // does not matter and neither does arriving twice: importing the same
60 // bytes again is a no-op, which is why at-least-once delivery is enough.
61 ada.import(&from_bo.bytes)?;
62 bo.import(&from_ada.bytes)?;
63 bo.import(&from_ada.bytes)?;
64
65 let ada_state = ada.export_json()?;
66 let bo_state = bo.export_json()?;
67
68 println!("together, Ada: {ada_state}");
69 println!("together, Bo: {bo_state}");
70 println!("identical: {}", ada_state == bo_state);
71
72 // Read that state against the four collection types:
73 //
74 // - `meta.title` is a map key two people wrote. One value wins, both
75 // replicas pick the same one, and neither ends up holding half of each.
76 // That is the reason to put independently editable fields in separate
77 // keys rather than in one JSON blob.
78 // - `packing` kept every insertion. A list loses nothing to a concurrent
79 // push.
80 // - `notes` merged at character level, so both sentences survive.
81 // - `edits` is 2. Two offline increments of 1 add up rather than
82 // overwriting each other, which is what makes a counter a counter and
83 // not an integer in a map.
84 println!("packing: {} entries", ada.list_len("packing")?);
85 println!("notes: {:?}", ada.text_value("notes")?);
86 println!("edits: {}", ada.counter_value("edits")?);
87
88 Ok(())
89}Trait Implementations§
Auto Trait Implementations§
impl Freeze for DataDoc
impl RefUnwindSafe for DataDoc
impl Send for DataDoc
impl Sync for DataDoc
impl Unpin for DataDoc
impl UnsafeUnpin for DataDoc
impl UnwindSafe for DataDoc
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere
T: ?Sized,
Source§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the foreground set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red() and
green(), which have the same functionality but are
pithier.
§Example
Set foreground color to white using fg():
use yansi::{Paint, Color};
painted.fg(Color::White);Set foreground color to white using white().
use yansi::Paint;
painted.white();Source§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
Source§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
Source§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
Source§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
Source§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
Source§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
Source§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
Source§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
Source§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self with the background set to
value.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red() and
on_green(), which have the same functionality but
are pithier.
§Example
Set background color to red using fg():
use yansi::{Paint, Color};
painted.bg(Color::Red);Set background color to red using on_red().
use yansi::Paint;
painted.on_red();Source§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
Source§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
Source§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
Source§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
Source§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
Source§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
Source§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
Source§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
Source§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
Source§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
Source§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling Attribute value.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold() and
underline(), which have the same functionality
but are pithier.
§Example
Make text bold using attr():
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);Make text bold using using bold().
use yansi::Paint;
painted.bold();Source§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
Source§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi Quirk value.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask() and
wrap(), which have the same functionality but are
pithier.
§Example
Enable wrapping using .quirk():
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);Enable wrapping using wrap().
use yansi::Paint;
painted.wrap();Source§fn clear(&self) -> Painted<&T>
👎Deprecated since 1.0.1: renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
fn clear(&self) -> Painted<&T>
renamed to resetting() due to conflicts with Vec::clear().
The clear() method will be removed in a future release.
Source§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the Condition value
applies. Replaces any previous condition.
See the crate level docs for more details.
§Example
Enable styling painted only when both stdout and stderr are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);