mirror of
https://github.com/loro-dev/loro.git
synced 2025-01-22 21:07:43 +00:00
feat: decode import blob meta (#307)
* feat: decode import blob meta * chore: rm logs
This commit is contained in:
parent
3c124f454b
commit
3da28459b0
11 changed files with 448 additions and 51 deletions
40
crates/fuzz/fuzz/Cargo.lock
generated
40
crates/fuzz/fuzz/Cargo.lock
generated
|
@ -389,7 +389,6 @@ dependencies = [
|
||||||
"append-only-bytes",
|
"append-only-bytes",
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"arref",
|
"arref",
|
||||||
"debug-log",
|
|
||||||
"enum-as-inner 0.5.1",
|
"enum-as-inner 0.5.1",
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
@ -414,6 +413,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tabled",
|
"tabled",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -432,7 +432,6 @@ version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"append-only-bytes",
|
"append-only-bytes",
|
||||||
"arref",
|
"arref",
|
||||||
"debug-log",
|
|
||||||
"enum-as-inner 0.6.0",
|
"enum-as-inner 0.6.0",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"num",
|
"num",
|
||||||
|
@ -598,6 +597,12 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postcard"
|
name = "postcard"
|
||||||
version = "1.0.8"
|
version = "1.0.8"
|
||||||
|
@ -921,6 +926,37 @@ dependencies = [
|
||||||
"syn 2.0.50",
|
"syn 2.0.50",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.50",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.17.0"
|
version = "1.17.0"
|
||||||
|
|
56
crates/loro-internal/fuzz/Cargo.lock
generated
56
crates/loro-internal/fuzz/Cargo.lock
generated
|
@ -133,15 +133,6 @@ dependencies = [
|
||||||
"syn 2.0.25",
|
"syn 2.0.25",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "debug-log"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "caf861b629ec23fc562cccc8b47cc98a54d192ecf4e7b575ce30659d8c814c3a"
|
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_arbitrary"
|
name = "derive_arbitrary"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -348,6 +339,7 @@ dependencies = [
|
||||||
"enum-as-inner 0.6.0",
|
"enum-as-inner 0.6.0",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"loro-rle",
|
"loro-rle",
|
||||||
|
"nonmax",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_columnar",
|
"serde_columnar",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
|
@ -361,7 +353,6 @@ dependencies = [
|
||||||
"append-only-bytes",
|
"append-only-bytes",
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"arref",
|
"arref",
|
||||||
"debug-log",
|
|
||||||
"enum-as-inner 0.5.1",
|
"enum-as-inner 0.5.1",
|
||||||
"enum_dispatch",
|
"enum_dispatch",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
|
@ -386,6 +377,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"tabled",
|
"tabled",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -412,7 +404,6 @@ version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"append-only-bytes",
|
"append-only-bytes",
|
||||||
"arref",
|
"arref",
|
||||||
"debug-log",
|
|
||||||
"enum-as-inner 0.6.0",
|
"enum-as-inner 0.6.0",
|
||||||
"fxhash",
|
"fxhash",
|
||||||
"num",
|
"num",
|
||||||
|
@ -437,6 +428,12 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nonmax"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num"
|
name = "num"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -573,6 +570,12 @@ dependencies = [
|
||||||
"siphasher",
|
"siphasher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postcard"
|
name = "postcard"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
|
@ -895,6 +898,37 @@ dependencies = [
|
||||||
"syn 2.0.25",
|
"syn 2.0.25",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||||
|
dependencies = [
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-attributes",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-attributes"
|
||||||
|
version = "0.1.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.25",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.16.0"
|
version = "1.16.0"
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
mod encode_reordered;
|
mod encode_reordered;
|
||||||
|
|
||||||
use crate::op::OpWithId;
|
use crate::op::OpWithId;
|
||||||
|
use crate::version::Frontiers;
|
||||||
use crate::LoroDoc;
|
use crate::LoroDoc;
|
||||||
use crate::{oplog::OpLog, LoroError, VersionVector};
|
use crate::{oplog::OpLog, LoroError, VersionVector};
|
||||||
use loro_common::{IdLpSpan, LoroResult};
|
use loro_common::{IdLpSpan, LoroResult};
|
||||||
use num_traits::{FromPrimitive, ToPrimitive};
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
use rle::{HasLength, Sliceable};
|
use rle::{HasLength, Sliceable};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
const MAGIC_BYTES: [u8; 4] = *b"loro";
|
const MAGIC_BYTES: [u8; 4] = *b"loro";
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
@ -230,3 +232,31 @@ pub(crate) fn decode_snapshot(
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ImportBlobMetadata {
|
||||||
|
/// The partial start version vector.
|
||||||
|
///
|
||||||
|
/// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
|
||||||
|
/// However, it does not constitute a complete version vector, as it only contains counters
|
||||||
|
/// from peers included within the import blob.
|
||||||
|
pub partial_start_vv: VersionVector,
|
||||||
|
/// The partial end version vector.
|
||||||
|
///
|
||||||
|
/// Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
|
||||||
|
/// However, it does not constitute a complete version vector, as it only contains counters
|
||||||
|
/// from peers included within the import blob.
|
||||||
|
pub partial_end_vv: VersionVector,
|
||||||
|
pub start_timestamp: i64,
|
||||||
|
pub start_frontiers: Frontiers,
|
||||||
|
pub end_timestamp: i64,
|
||||||
|
pub change_num: u32,
|
||||||
|
pub is_snapshot: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoroDoc {
|
||||||
|
/// Decodes the metadata for an imported blob from the provided bytes.
|
||||||
|
pub fn decode_import_blob_meta(blob: &[u8]) -> LoroResult<ImportBlobMetadata> {
|
||||||
|
encode_reordered::decode_import_blob_meta(blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serde_columnar::columnar;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arena::SharedArena,
|
arena::SharedArena,
|
||||||
change::{Change, Lamport},
|
change::{Change, Lamport, Timestamp},
|
||||||
container::{idx::ContainerIdx, list::list_op::DeleteSpanWithId, richtext::TextStyleInfoFlag},
|
container::{idx::ContainerIdx, list::list_op::DeleteSpanWithId, richtext::TextStyleInfoFlag},
|
||||||
encoding::{
|
encoding::{
|
||||||
encode_reordered::value::{ValueKind, ValueWriter},
|
encode_reordered::value::{ValueKind, ValueWriter},
|
||||||
|
@ -31,6 +31,8 @@ use self::{
|
||||||
value::ValueReader,
|
value::ValueReader,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::{parse_header_and_body, ImportBlobMetadata};
|
||||||
|
|
||||||
/// If any section of the document is longer than this, we will not decode it.
|
/// If any section of the document is longer than this, we will not decode it.
|
||||||
/// It will return an data corruption error instead.
|
/// It will return an data corruption error instead.
|
||||||
const MAX_DECODED_SIZE: usize = 1 << 30;
|
const MAX_DECODED_SIZE: usize = 1 << 30;
|
||||||
|
@ -39,6 +41,24 @@ const MAX_DECODED_SIZE: usize = 1 << 30;
|
||||||
const MAX_COLLECTION_SIZE: usize = 1 << 28;
|
const MAX_COLLECTION_SIZE: usize = 1 << 28;
|
||||||
|
|
||||||
pub(crate) fn encode_updates(oplog: &OpLog, vv: &VersionVector) -> Vec<u8> {
|
pub(crate) fn encode_updates(oplog: &OpLog, vv: &VersionVector) -> Vec<u8> {
|
||||||
|
// skip the ops that current oplog does not have
|
||||||
|
let actual_start_vv: VersionVector = vv
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(&peer, &end_counter)| {
|
||||||
|
if end_counter == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let this_end = oplog.vv().get(&peer).cloned().unwrap_or(0);
|
||||||
|
if this_end <= end_counter {
|
||||||
|
return Some((peer, this_end));
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((peer, end_counter))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let vv = &actual_start_vv;
|
||||||
let mut peer_register: ValueRegister<PeerID> = ValueRegister::new();
|
let mut peer_register: ValueRegister<PeerID> = ValueRegister::new();
|
||||||
let mut key_register: ValueRegister<InternalString> = ValueRegister::new();
|
let mut key_register: ValueRegister<InternalString> = ValueRegister::new();
|
||||||
let (start_counters, diff_changes) = init_encode(oplog, vv, &mut peer_register);
|
let (start_counters, diff_changes) = init_encode(oplog, vv, &mut peer_register);
|
||||||
|
@ -91,7 +111,8 @@ pub(crate) fn encode_updates(oplog: &OpLog, vv: &VersionVector) -> Vec<u8> {
|
||||||
);
|
);
|
||||||
|
|
||||||
let frontiers = oplog
|
let frontiers = oplog
|
||||||
.frontiers()
|
.dag
|
||||||
|
.vv_to_frontiers(&actual_start_vv)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| (peer_register.register(&x.peer), x.counter))
|
.map(|x| (peer_register.register(&x.peer), x.counter))
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -109,7 +130,7 @@ pub(crate) fn encode_updates(oplog: &OpLog, vv: &VersionVector) -> Vec<u8> {
|
||||||
dep_arena,
|
dep_arena,
|
||||||
&[],
|
&[],
|
||||||
)),
|
)),
|
||||||
frontiers,
|
start_frontiers: frontiers,
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_columnar::to_vec(&doc).unwrap()
|
serde_columnar::to_vec(&doc).unwrap()
|
||||||
|
@ -147,6 +168,62 @@ pub(crate) fn decode_updates(oplog: &mut OpLog, bytes: &[u8]) -> LoroResult<()>
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_import_blob_meta(bytes: &[u8]) -> LoroResult<ImportBlobMetadata> {
|
||||||
|
let parsed = parse_header_and_body(bytes)?;
|
||||||
|
let is_snapshot = parsed.mode.is_snapshot();
|
||||||
|
let iterators = serde_columnar::iter_from_bytes::<EncodedDoc>(parsed.body)?;
|
||||||
|
let DecodedArenas { peer_ids, .. } = decode_arena(&iterators.arenas)?;
|
||||||
|
let start_vv: VersionVector = iterators
|
||||||
|
.start_counters
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(peer_idx, counter)| {
|
||||||
|
if *counter == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ID::new(peer_ids.peer_ids[peer_idx], *counter - 1))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let frontiers = iterators
|
||||||
|
.start_frontiers
|
||||||
|
.iter()
|
||||||
|
.map(|x| ID::new(peer_ids.peer_ids[x.0], x.1))
|
||||||
|
.collect();
|
||||||
|
let mut end_vv_counters = iterators.start_counters;
|
||||||
|
let mut change_num = 0;
|
||||||
|
let mut start_timestamp = Timestamp::MAX;
|
||||||
|
let mut end_timestamp = Timestamp::MIN;
|
||||||
|
|
||||||
|
for EncodedChange {
|
||||||
|
peer_idx,
|
||||||
|
len,
|
||||||
|
timestamp,
|
||||||
|
..
|
||||||
|
} in iterators.changes
|
||||||
|
{
|
||||||
|
end_vv_counters[peer_idx] += len as Counter;
|
||||||
|
start_timestamp = start_timestamp.min(timestamp);
|
||||||
|
end_timestamp = end_timestamp.max(timestamp);
|
||||||
|
change_num += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ImportBlobMetadata {
|
||||||
|
is_snapshot,
|
||||||
|
start_frontiers: frontiers,
|
||||||
|
partial_start_vv: start_vv,
|
||||||
|
partial_end_vv: VersionVector::from_iter(
|
||||||
|
end_vv_counters
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(peer_idx, counter)| ID::new(peer_ids.peer_ids[peer_idx], *counter - 1)),
|
||||||
|
),
|
||||||
|
start_timestamp,
|
||||||
|
end_timestamp,
|
||||||
|
change_num,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn import_changes_to_oplog(
|
fn import_changes_to_oplog(
|
||||||
changes: Vec<Change>,
|
changes: Vec<Change>,
|
||||||
oplog: &mut OpLog,
|
oplog: &mut OpLog,
|
||||||
|
@ -459,11 +536,6 @@ pub(crate) fn encode_snapshot(oplog: &OpLog, state: &DocState, vv: &VersionVecto
|
||||||
&mut key_register,
|
&mut key_register,
|
||||||
);
|
);
|
||||||
|
|
||||||
let frontiers = oplog
|
|
||||||
.frontiers()
|
|
||||||
.iter()
|
|
||||||
.map(|x| (peer_register.register(&x.peer), x.counter))
|
|
||||||
.collect();
|
|
||||||
let doc = EncodedDoc {
|
let doc = EncodedDoc {
|
||||||
ops: encoded_ops,
|
ops: encoded_ops,
|
||||||
delete_starts: del_starts,
|
delete_starts: del_starts,
|
||||||
|
@ -478,7 +550,7 @@ pub(crate) fn encode_snapshot(oplog: &OpLog, state: &DocState, vv: &VersionVecto
|
||||||
dep_arena,
|
dep_arena,
|
||||||
&state_bytes,
|
&state_bytes,
|
||||||
)),
|
)),
|
||||||
frontiers,
|
start_frontiers: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
serde_columnar::to_vec(&doc).unwrap()
|
serde_columnar::to_vec(&doc).unwrap()
|
||||||
|
@ -641,19 +713,6 @@ pub(crate) fn decode_snapshot(doc: &LoroDoc, bytes: &[u8]) -> LoroResult<()> {
|
||||||
deps,
|
deps,
|
||||||
state_blob_arena,
|
state_blob_arena,
|
||||||
} = decode_arena(&iter.arenas)?;
|
} = decode_arena(&iter.arenas)?;
|
||||||
let frontiers: Frontiers = iter
|
|
||||||
.frontiers
|
|
||||||
.iter()
|
|
||||||
.map(|x| {
|
|
||||||
let peer = peer_ids
|
|
||||||
.peer_ids
|
|
||||||
.get(x.0)
|
|
||||||
.ok_or(LoroError::DecodeDataCorruptionError)?;
|
|
||||||
let ans: Result<ID, LoroError> = Ok(ID::new(*peer, x.1));
|
|
||||||
ans
|
|
||||||
})
|
|
||||||
.try_collect()?;
|
|
||||||
|
|
||||||
let ExtractedOps {
|
let ExtractedOps {
|
||||||
ops_map,
|
ops_map,
|
||||||
mut ops,
|
mut ops,
|
||||||
|
@ -679,7 +738,7 @@ pub(crate) fn decode_snapshot(doc: &LoroDoc, bytes: &[u8]) -> LoroResult<()> {
|
||||||
|
|
||||||
decode_snapshot_states(
|
decode_snapshot_states(
|
||||||
&mut state,
|
&mut state,
|
||||||
frontiers,
|
oplog.frontiers().clone(),
|
||||||
iter.states,
|
iter.states,
|
||||||
containers,
|
containers,
|
||||||
state_blob_arena,
|
state_blob_arena,
|
||||||
|
@ -1023,7 +1082,7 @@ mod encode {
|
||||||
peer_register: &mut ValueRegister<PeerID>,
|
peer_register: &mut ValueRegister<PeerID>,
|
||||||
) -> (Vec<i32>, Vec<Cow<'a, Change>>) {
|
) -> (Vec<i32>, Vec<Cow<'a, Change>>) {
|
||||||
let self_vv = oplog.vv();
|
let self_vv = oplog.vv();
|
||||||
let start_vv = vv.trim(&oplog.vv());
|
let start_vv = vv.trim(oplog.vv());
|
||||||
let mut start_counters = Vec::new();
|
let mut start_counters = Vec::new();
|
||||||
|
|
||||||
let mut diff_changes: Vec<Cow<'a, Change>> = Vec::new();
|
let mut diff_changes: Vec<Cow<'a, Change>> = Vec::new();
|
||||||
|
@ -1362,7 +1421,10 @@ struct EncodedDoc<'a> {
|
||||||
states: Vec<EncodedStateInfo>,
|
states: Vec<EncodedStateInfo>,
|
||||||
/// The first counter value for each change of each peer in `changes`
|
/// The first counter value for each change of each peer in `changes`
|
||||||
start_counters: Vec<Counter>,
|
start_counters: Vec<Counter>,
|
||||||
frontiers: Vec<(PeerIdx, Counter)>,
|
/// The frontiers at the start of this encoded delta.
|
||||||
|
///
|
||||||
|
/// It's empty when the encoding mode is snapshot.
|
||||||
|
start_frontiers: Vec<(PeerIdx, Counter)>,
|
||||||
#[columnar(borrow)]
|
#[columnar(borrow)]
|
||||||
raw_values: Cow<'a, [u8]>,
|
raw_values: Cow<'a, [u8]>,
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ pub mod change;
|
||||||
pub mod configure;
|
pub mod configure;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
pub mod dag;
|
pub mod dag;
|
||||||
mod encoding;
|
pub mod encoding;
|
||||||
pub mod id;
|
pub mod id;
|
||||||
pub mod op;
|
pub mod op;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
|
@ -750,7 +750,7 @@ impl VersionVector {
|
||||||
shrink_frontiers(last_ids, dag)
|
shrink_frontiers(last_ids, dag)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn trim(&self, vv: &&VersionVector) -> VersionVector {
|
pub(crate) fn trim(&self, vv: &VersionVector) -> VersionVector {
|
||||||
let mut ans = VersionVector::new();
|
let mut ans = VersionVector::new();
|
||||||
for (client_id, &counter) in self.iter() {
|
for (client_id, &counter) in self.iter() {
|
||||||
if let Some(&other_counter) = vv.get(client_id) {
|
if let Some(&other_counter) = vv.get(client_id) {
|
||||||
|
@ -800,6 +800,10 @@ fn shrink_frontiers(mut last_ids: Vec<ID>, dag: &AppDag) -> Frontiers {
|
||||||
let mut frontiers = Frontiers::default();
|
let mut frontiers = Frontiers::default();
|
||||||
let mut frontiers_vv = Vec::new();
|
let mut frontiers_vv = Vec::new();
|
||||||
|
|
||||||
|
if last_ids.is_empty() {
|
||||||
|
return frontiers;
|
||||||
|
}
|
||||||
|
|
||||||
if last_ids.len() == 1 {
|
if last_ids.len() == 1 {
|
||||||
frontiers.push(last_ids[0]);
|
frontiers.push(last_ids[0]);
|
||||||
return frontiers;
|
return frontiers;
|
||||||
|
@ -857,7 +861,11 @@ impl From<Vec<ID>> for VersionVector {
|
||||||
|
|
||||||
impl FromIterator<ID> for VersionVector {
|
impl FromIterator<ID> for VersionVector {
|
||||||
fn from_iter<T: IntoIterator<Item = ID>>(iter: T) -> Self {
|
fn from_iter<T: IntoIterator<Item = ID>>(iter: T) -> Self {
|
||||||
let mut vv = VersionVector::new();
|
let iter = iter.into_iter();
|
||||||
|
let mut vv = VersionVector(FxHashMap::with_capacity_and_hasher(
|
||||||
|
iter.size_hint().0,
|
||||||
|
Default::default(),
|
||||||
|
));
|
||||||
for id in iter {
|
for id in iter {
|
||||||
vv.set_last(id);
|
vv.set_last(id);
|
||||||
}
|
}
|
||||||
|
@ -866,6 +874,12 @@ impl FromIterator<ID> for VersionVector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromIterator<(PeerID, Counter)> for VersionVector {
|
||||||
|
fn from_iter<T: IntoIterator<Item = (PeerID, Counter)>>(iter: T) -> Self {
|
||||||
|
VersionVector(FxHashMap::from_iter(iter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Note: It will be encoded into binary format, so the order of its fields should not be changed.
|
// Note: It will be encoded into binary format, so the order of its fields should not be changed.
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub(crate) struct TotalOrderStamp {
|
pub(crate) struct TotalOrderStamp {
|
||||||
|
|
|
@ -2,12 +2,16 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use js_sys::{Array, Object, Reflect, Uint8Array};
|
use js_sys::{Array, Object, Reflect, Uint8Array};
|
||||||
use loro_internal::delta::{DeltaItem, ResolvedMapDelta};
|
use loro_internal::delta::{DeltaItem, ResolvedMapDelta};
|
||||||
|
use loro_internal::encoding::ImportBlobMetadata;
|
||||||
use loro_internal::event::Diff;
|
use loro_internal::event::Diff;
|
||||||
use loro_internal::handler::{Handler, ValueOrHandler};
|
use loro_internal::handler::{Handler, ValueOrHandler};
|
||||||
use loro_internal::{LoroDoc, LoroValue};
|
use loro_internal::{LoroDoc, LoroValue};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
use crate::{Container, JsContainer, LoroList, LoroMap, LoroText, LoroTree};
|
use crate::{
|
||||||
|
frontiers_to_ids, Container, JsContainer, JsImportBlobMetadata, LoroList, LoroMap, LoroText,
|
||||||
|
LoroTree,
|
||||||
|
};
|
||||||
use wasm_bindgen::__rt::IntoJsResult;
|
use wasm_bindgen::__rt::IntoJsResult;
|
||||||
use wasm_bindgen::convert::RefFromWasmAbi;
|
use wasm_bindgen::convert::RefFromWasmAbi;
|
||||||
|
|
||||||
|
@ -198,6 +202,35 @@ pub fn convert(value: LoroValue) -> JsValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ImportBlobMetadata> for JsImportBlobMetadata {
|
||||||
|
fn from(meta: ImportBlobMetadata) -> Self {
|
||||||
|
let start_vv = super::VersionVector(meta.partial_start_vv);
|
||||||
|
let end_vv = super::VersionVector(meta.partial_end_vv);
|
||||||
|
let start_vv: JsValue = start_vv.into();
|
||||||
|
let end_vv: JsValue = end_vv.into();
|
||||||
|
let start_timestamp: JsValue = JsValue::from_f64(meta.start_timestamp as f64);
|
||||||
|
let end_timestamp: JsValue = JsValue::from_f64(meta.end_timestamp as f64);
|
||||||
|
let is_snapshot: JsValue = JsValue::from_bool(meta.is_snapshot);
|
||||||
|
let change_num: JsValue = JsValue::from_f64(meta.change_num as f64);
|
||||||
|
let ans = Object::new();
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&ans,
|
||||||
|
&JsValue::from_str("partialStartVersionVector"),
|
||||||
|
&start_vv,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("partialEndVersionVector"), &end_vv).unwrap();
|
||||||
|
let js_frontiers: JsValue = frontiers_to_ids(&meta.start_frontiers).into();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("startFrontiers"), &js_frontiers).unwrap();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("startTimestamp"), &start_timestamp).unwrap();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("endTimestamp"), &end_timestamp).unwrap();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("isSnapshot"), &is_snapshot).unwrap();
|
||||||
|
js_sys::Reflect::set(&ans, &JsValue::from_str("changeNum"), &change_num).unwrap();
|
||||||
|
let ans: JsValue = ans.into();
|
||||||
|
ans.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn map_delta_to_js(value: &ResolvedMapDelta, doc: &Arc<LoroDoc>) -> JsValue {
|
fn map_delta_to_js(value: &ResolvedMapDelta, doc: &Arc<LoroDoc>) -> JsValue {
|
||||||
let obj = Object::new();
|
let obj = Object::new();
|
||||||
for (key, value) in value.updated.iter() {
|
for (key, value) in value.updated.iter() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ use loro_internal::{
|
||||||
change::Lamport,
|
change::Lamport,
|
||||||
configure::{StyleConfig, StyleConfigMap},
|
configure::{StyleConfig, StyleConfigMap},
|
||||||
container::{richtext::ExpandType, ContainerID},
|
container::{richtext::ExpandType, ContainerID},
|
||||||
|
encoding::ImportBlobMetadata,
|
||||||
event::Index,
|
event::Index,
|
||||||
handler::{
|
handler::{
|
||||||
Handler, ListHandler, MapHandler, TextDelta, TextHandler, TreeHandler, ValueOrHandler,
|
Handler, ListHandler, MapHandler, TextDelta, TextHandler, TreeHandler, ValueOrHandler,
|
||||||
|
@ -70,6 +71,8 @@ extern "C" {
|
||||||
pub type JsOrigin;
|
pub type JsOrigin;
|
||||||
#[wasm_bindgen(typescript_type = "{ peer: PeerID, counter: number }")]
|
#[wasm_bindgen(typescript_type = "{ peer: PeerID, counter: number }")]
|
||||||
pub type JsID;
|
pub type JsID;
|
||||||
|
#[wasm_bindgen(typescript_type = "{ peer: PeerID, counter: number }[]")]
|
||||||
|
pub type JsIDs;
|
||||||
#[wasm_bindgen(typescript_type = "{ start: number, end: number }")]
|
#[wasm_bindgen(typescript_type = "{ start: number, end: number }")]
|
||||||
pub type JsRange;
|
pub type JsRange;
|
||||||
#[wasm_bindgen(typescript_type = "number|bool|string|null")]
|
#[wasm_bindgen(typescript_type = "number|bool|string|null")]
|
||||||
|
@ -118,6 +121,8 @@ extern "C" {
|
||||||
pub type JsPartialOrd;
|
pub type JsPartialOrd;
|
||||||
#[wasm_bindgen(typescript_type = "'Tree'|'Map'|'List'|'Text'")]
|
#[wasm_bindgen(typescript_type = "'Tree'|'Map'|'List'|'Text'")]
|
||||||
pub type JsContainerKind;
|
pub type JsContainerKind;
|
||||||
|
#[wasm_bindgen(typescript_type = "ImportBlobMetadata")]
|
||||||
|
pub type JsImportBlobMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod observer {
|
mod observer {
|
||||||
|
@ -173,17 +178,18 @@ fn js_id_to_id(id: JsID) -> Result<ID, JsValue> {
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frontiers_to_ids(frontiers: &Frontiers) -> Vec<JsID> {
|
fn frontiers_to_ids(frontiers: &Frontiers) -> JsIDs {
|
||||||
let mut ans = Vec::with_capacity(frontiers.len());
|
let js_arr = Array::new();
|
||||||
for id in frontiers.iter() {
|
for id in frontiers.iter() {
|
||||||
let obj = Object::new();
|
let obj = Object::new();
|
||||||
Reflect::set(&obj, &"peer".into(), &id.peer.to_string().into()).unwrap();
|
Reflect::set(&obj, &"peer".into(), &id.peer.to_string().into()).unwrap();
|
||||||
Reflect::set(&obj, &"counter".into(), &id.counter.into()).unwrap();
|
Reflect::set(&obj, &"counter".into(), &id.counter.into()).unwrap();
|
||||||
let value: JsValue = obj.into_js_result().unwrap();
|
let value: JsValue = obj.into_js_result().unwrap();
|
||||||
ans.push(value.into());
|
js_arr.push(&value);
|
||||||
}
|
}
|
||||||
|
|
||||||
ans
|
let value: JsValue = js_arr.into();
|
||||||
|
value.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn js_value_to_container_id(
|
fn js_value_to_container_id(
|
||||||
|
@ -655,7 +661,7 @@ impl Loro {
|
||||||
///
|
///
|
||||||
/// If you checkout to a specific version, this value will change.
|
/// If you checkout to a specific version, this value will change.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn frontiers(&self) -> Vec<JsID> {
|
pub fn frontiers(&self) -> JsIDs {
|
||||||
frontiers_to_ids(&self.0.state_frontiers())
|
frontiers_to_ids(&self.0.state_frontiers())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -663,7 +669,8 @@ impl Loro {
|
||||||
///
|
///
|
||||||
/// If you checkout to a specific version, this value will not change.
|
/// If you checkout to a specific version, this value will not change.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn oplog_frontiers(&self) -> Vec<JsID> {
|
#[wasm_bindgen(js_name = "oplogFrontiers")]
|
||||||
|
pub fn oplog_frontiers(&self) -> JsIDs {
|
||||||
frontiers_to_ids(&self.0.oplog_frontiers())
|
frontiers_to_ids(&self.0.oplog_frontiers())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1061,7 +1068,7 @@ impl Loro {
|
||||||
/// const frontiers = doc.vvToFrontiers(version);
|
/// const frontiers = doc.vvToFrontiers(version);
|
||||||
/// ```
|
/// ```
|
||||||
#[wasm_bindgen(js_name = "vvToFrontiers")]
|
#[wasm_bindgen(js_name = "vvToFrontiers")]
|
||||||
pub fn vv_to_frontiers(&self, vv: &VersionVector) -> JsResult<Vec<JsID>> {
|
pub fn vv_to_frontiers(&self, vv: &VersionVector) -> JsResult<JsIDs> {
|
||||||
let f = self.0.oplog().lock().unwrap().dag().vv_to_frontiers(&vv.0);
|
let f = self.0.oplog().lock().unwrap().dag().vv_to_frontiers(&vv.0);
|
||||||
Ok(frontiers_to_ids(&f))
|
Ok(frontiers_to_ids(&f))
|
||||||
}
|
}
|
||||||
|
@ -2614,6 +2621,22 @@ impl Container {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decode the metadata of the import blob.
|
||||||
|
///
|
||||||
|
/// This method is useful to get the following metadata of the import blob:
|
||||||
|
///
|
||||||
|
/// - startVersionVector
|
||||||
|
/// - endVersionVector
|
||||||
|
/// - startTimestamp
|
||||||
|
/// - endTimestamp
|
||||||
|
/// - isSnapshot
|
||||||
|
/// - changeNum
|
||||||
|
#[wasm_bindgen(js_name = "decodeImportBlobMeta")]
|
||||||
|
pub fn decode_import_blob_meta(blob: &[u8]) -> JsResult<JsImportBlobMetadata> {
|
||||||
|
let meta: ImportBlobMetadata = LoroDoc::decode_import_blob_meta(blob)?;
|
||||||
|
Ok(meta.into())
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen(typescript_custom_section)]
|
#[wasm_bindgen(typescript_custom_section)]
|
||||||
const TYPES: &'static str = r#"
|
const TYPES: &'static str = r#"
|
||||||
/**
|
/**
|
||||||
|
@ -2730,4 +2753,29 @@ export type Value =
|
||||||
| Value[];
|
| Value[];
|
||||||
|
|
||||||
export type Container = LoroList | LoroMap | LoroText | LoroTree;
|
export type Container = LoroList | LoroMap | LoroText | LoroTree;
|
||||||
|
|
||||||
|
export interface ImportBlobMetadata {
|
||||||
|
/**
|
||||||
|
* The version vector of the start of the import.
|
||||||
|
*
|
||||||
|
* Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
|
||||||
|
* However, it does not constitute a complete version vector, as it only contains counters
|
||||||
|
* from peers included within the import blob.
|
||||||
|
*/
|
||||||
|
partialStartVersionVector: VersionVector;
|
||||||
|
/**
|
||||||
|
* The version vector of the end of the import.
|
||||||
|
*
|
||||||
|
* Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`.
|
||||||
|
* However, it does not constitute a complete version vector, as it only contains counters
|
||||||
|
* from peers included within the import blob.
|
||||||
|
*/
|
||||||
|
partialEndVersionVector: VersionVector;
|
||||||
|
|
||||||
|
startFrontiers: OpId[],
|
||||||
|
startTimestamp: number;
|
||||||
|
endTimestamp: number;
|
||||||
|
isSnapshot: boolean;
|
||||||
|
changeNum: number;
|
||||||
|
}
|
||||||
"#;
|
"#;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use either::Either;
|
||||||
use event::{DiffEvent, Subscriber};
|
use event::{DiffEvent, Subscriber};
|
||||||
use loro_internal::change::Timestamp;
|
use loro_internal::change::Timestamp;
|
||||||
use loro_internal::container::IntoContainerId;
|
use loro_internal::container::IntoContainerId;
|
||||||
|
use loro_internal::encoding::ImportBlobMetadata;
|
||||||
use loro_internal::handler::HandlerTrait;
|
use loro_internal::handler::HandlerTrait;
|
||||||
use loro_internal::handler::ValueOrHandler;
|
use loro_internal::handler::ValueOrHandler;
|
||||||
use loro_internal::LoroDoc as InnerLoroDoc;
|
use loro_internal::LoroDoc as InnerLoroDoc;
|
||||||
|
@ -58,6 +59,11 @@ impl LoroDoc {
|
||||||
self.doc.config()
|
self.doc.config()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Decodes the metadata for an imported blob from the provided bytes.
|
||||||
|
pub fn decode_import_blob_meta(bytes: &[u8]) -> LoroResult<ImportBlobMetadata> {
|
||||||
|
InnerLoroDoc::decode_import_blob_meta(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set whether to record the timestamp of each change. Default is `false`.
|
/// Set whether to record the timestamp of each change. Default is `false`.
|
||||||
///
|
///
|
||||||
/// If enabled, the Unix timestamp will be recorded for each change automatically.
|
/// If enabled, the Unix timestamp will be recorded for each change automatically.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{cmp::Ordering, sync::Arc};
|
use std::{cmp::Ordering, sync::Arc};
|
||||||
|
|
||||||
use loro::{FrontiersNotIncluded, LoroDoc, LoroError, LoroList, LoroMap, LoroText, ToJson};
|
use loro::{FrontiersNotIncluded, LoroDoc, LoroError, LoroList, LoroMap, LoroText, ToJson};
|
||||||
use loro_internal::{handler::TextDelta, id::ID, LoroResult};
|
use loro_internal::{handler::TextDelta, id::ID, vv, LoroResult};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -439,6 +439,72 @@ fn prelim_support() -> LoroResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decode_import_blob_meta() -> LoroResult<()> {
|
||||||
|
let doc_1 = LoroDoc::new();
|
||||||
|
doc_1.set_peer_id(1)?;
|
||||||
|
doc_1.get_text("text").insert(0, "123")?;
|
||||||
|
{
|
||||||
|
let bytes = doc_1.export_from(&Default::default());
|
||||||
|
let meta = LoroDoc::decode_import_blob_meta(&bytes).unwrap();
|
||||||
|
assert!(meta.partial_start_vv.is_empty());
|
||||||
|
assert_eq!(meta.partial_end_vv, vv!(1 => 3));
|
||||||
|
assert_eq!(meta.start_timestamp, 0);
|
||||||
|
assert_eq!(meta.end_timestamp, 0);
|
||||||
|
assert!(!meta.is_snapshot);
|
||||||
|
assert!(meta.start_frontiers.is_empty());
|
||||||
|
assert_eq!(meta.change_num, 1);
|
||||||
|
|
||||||
|
let bytes = doc_1.export_snapshot();
|
||||||
|
let meta = LoroDoc::decode_import_blob_meta(&bytes).unwrap();
|
||||||
|
assert!(meta.partial_start_vv.is_empty());
|
||||||
|
assert_eq!(meta.partial_end_vv, vv!(1 => 3));
|
||||||
|
assert_eq!(meta.start_timestamp, 0);
|
||||||
|
assert_eq!(meta.end_timestamp, 0);
|
||||||
|
assert!(meta.is_snapshot);
|
||||||
|
assert!(meta.start_frontiers.is_empty());
|
||||||
|
assert_eq!(meta.change_num, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let doc_2 = LoroDoc::new();
|
||||||
|
doc_2.set_peer_id(2)?;
|
||||||
|
doc_2.import(&doc_1.export_snapshot()).unwrap();
|
||||||
|
doc_2.get_text("text").insert(0, "123")?;
|
||||||
|
doc_2.get_text("text").insert(0, "123")?;
|
||||||
|
{
|
||||||
|
let bytes = doc_2.export_from(&doc_1.oplog_vv());
|
||||||
|
let meta = LoroDoc::decode_import_blob_meta(&bytes).unwrap();
|
||||||
|
assert_eq!(meta.partial_start_vv, vv!());
|
||||||
|
assert_eq!(meta.partial_end_vv, vv!(2 => 6));
|
||||||
|
assert_eq!(meta.start_timestamp, 0);
|
||||||
|
assert_eq!(meta.end_timestamp, 0);
|
||||||
|
assert!(!meta.is_snapshot);
|
||||||
|
assert_eq!(meta.start_frontiers, vec![ID::new(1, 2)].into());
|
||||||
|
assert_eq!(meta.change_num, 1);
|
||||||
|
|
||||||
|
let bytes = doc_2.export_from(&vv!(1 => 1));
|
||||||
|
let meta = LoroDoc::decode_import_blob_meta(&bytes).unwrap();
|
||||||
|
assert_eq!(meta.partial_start_vv, vv!(1 => 1));
|
||||||
|
assert_eq!(meta.partial_end_vv, vv!(1 => 3, 2 => 6));
|
||||||
|
assert_eq!(meta.start_timestamp, 0);
|
||||||
|
assert_eq!(meta.end_timestamp, 0);
|
||||||
|
assert!(!meta.is_snapshot);
|
||||||
|
assert_eq!(meta.start_frontiers, vec![ID::new(1, 0)].into());
|
||||||
|
assert_eq!(meta.change_num, 2);
|
||||||
|
|
||||||
|
let bytes = doc_2.export_snapshot();
|
||||||
|
let meta = LoroDoc::decode_import_blob_meta(&bytes).unwrap();
|
||||||
|
assert_eq!(meta.partial_start_vv, vv!());
|
||||||
|
assert_eq!(meta.partial_end_vv, vv!(1 => 3, 2 => 6));
|
||||||
|
assert_eq!(meta.start_timestamp, 0);
|
||||||
|
assert_eq!(meta.end_timestamp, 0);
|
||||||
|
assert!(meta.is_snapshot);
|
||||||
|
assert!(meta.start_frontiers.is_empty());
|
||||||
|
assert_eq!(meta.change_num, 2);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_example() {
|
fn init_example() {
|
||||||
// create meta/users/0/new_user/{name: string, bio: Text}
|
// create meta/users/0/new_user/{name: string, bio: Text}
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { Loro, LoroMap, OpId, VersionVector } from "../src";
|
import {
|
||||||
|
decodeImportBlobMeta,
|
||||||
|
Loro,
|
||||||
|
LoroMap,
|
||||||
|
OpId,
|
||||||
|
VersionVector,
|
||||||
|
} from "../src";
|
||||||
|
|
||||||
describe("Frontiers", () => {
|
describe("Frontiers", () => {
|
||||||
it("two clients", () => {
|
it("two clients", () => {
|
||||||
|
@ -134,3 +140,65 @@ describe("Version", () => {
|
||||||
expect(change.length).toBe(1);
|
expect(change.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("get import blob metadata", () => {
|
||||||
|
const doc0 = new Loro();
|
||||||
|
doc0.setPeerId(0n);
|
||||||
|
const text = doc0.getText("text");
|
||||||
|
text.insert(0, "0");
|
||||||
|
doc0.commit();
|
||||||
|
{
|
||||||
|
const bytes = doc0.exportFrom();
|
||||||
|
const meta = decodeImportBlobMeta(bytes);
|
||||||
|
expect(meta.changeNum).toBe(1);
|
||||||
|
expect(meta.partialStartVersionVector.get("0")).toBeFalsy();
|
||||||
|
expect(meta.partialEndVersionVector.get("0")).toBe(1);
|
||||||
|
expect(meta.startTimestamp).toBe(0);
|
||||||
|
expect(meta.endTimestamp).toBe(0);
|
||||||
|
expect(meta.isSnapshot).toBeFalsy();
|
||||||
|
console.log(meta.startFrontiers);
|
||||||
|
expect(meta.startFrontiers.length).toBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc1 = new Loro();
|
||||||
|
doc1.setPeerId(1);
|
||||||
|
doc1.getText("text").insert(0, "123");
|
||||||
|
doc1.import(doc0.exportFrom());
|
||||||
|
{
|
||||||
|
const bytes = doc1.exportFrom();
|
||||||
|
const meta = decodeImportBlobMeta(bytes);
|
||||||
|
expect(meta.changeNum).toBe(2);
|
||||||
|
expect(meta.partialStartVersionVector.get("0")).toBeFalsy();
|
||||||
|
expect(meta.partialEndVersionVector.get("0")).toBe(1);
|
||||||
|
expect(meta.partialEndVersionVector.get("1")).toBe(3);
|
||||||
|
expect(meta.startTimestamp).toBe(0);
|
||||||
|
expect(meta.endTimestamp).toBe(0);
|
||||||
|
expect(meta.isSnapshot).toBeFalsy();
|
||||||
|
expect(meta.startFrontiers.length).toBe(0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const bytes = doc1.exportSnapshot();
|
||||||
|
const meta = decodeImportBlobMeta(bytes);
|
||||||
|
expect(meta.changeNum).toBe(2);
|
||||||
|
expect(meta.partialStartVersionVector.get("0")).toBeFalsy();
|
||||||
|
expect(meta.partialEndVersionVector.get("0")).toBe(1);
|
||||||
|
expect(meta.partialEndVersionVector.get("1")).toBe(3);
|
||||||
|
expect(meta.startTimestamp).toBe(0);
|
||||||
|
expect(meta.endTimestamp).toBe(0);
|
||||||
|
expect(meta.isSnapshot).toBeTruthy();
|
||||||
|
expect(meta.startFrontiers.length).toBe(0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const bytes = doc1.exportFrom(doc0.oplogVersion());
|
||||||
|
const meta = decodeImportBlobMeta(bytes);
|
||||||
|
expect(meta.changeNum).toBe(1);
|
||||||
|
expect(meta.partialStartVersionVector.get("0")).toBeUndefined();
|
||||||
|
expect(meta.partialStartVersionVector.get("1")).toBeFalsy();
|
||||||
|
expect(meta.partialEndVersionVector.get("0")).toBeUndefined();
|
||||||
|
expect(meta.partialEndVersionVector.get("1")).toBe(3);
|
||||||
|
expect(meta.startTimestamp).toBe(0);
|
||||||
|
expect(meta.endTimestamp).toBe(0);
|
||||||
|
expect(meta.isSnapshot).toBeFalsy();
|
||||||
|
expect(meta.startFrontiers).toStrictEqual([{ peer: "0", counter: 0 }]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue