///|
pub struct SubdocsEvent {
priv additions : ReadOnlyArray[Doc]
priv removals : ReadOnlyArray[Doc]
priv loads : ReadOnlyArray[Doc]
}
///|
pub fn SubdocsEvent::added(self : SubdocsEvent) -> ReadOnlyArray[Doc] {
self.additions
}
///|
pub fn SubdocsEvent::removed(self : SubdocsEvent) -> ReadOnlyArray[Doc] {
self.removals
}
///|
pub fn SubdocsEvent::loaded(self : SubdocsEvent) -> ReadOnlyArray[Doc] {
self.loads
}
///|
pub fn Doc::guid(self : Doc) -> String {
self.document_guid
}
///|
pub fn Doc::collection_id(self : Doc) -> String? {
self.collection
}
///|
pub fn Doc::auto_load(self : Doc) -> Bool {
self.auto_loading
}
///|
pub fn Doc::should_load(self : Doc) -> Bool {
self.loading
}
///|
pub fn Doc::parent_doc(self : Doc) -> Doc? {
self.owner.map(fn(p) { p.0 })
}
///|
pub fn Doc::subdocs(self : Doc) -> Array[Doc] {
self.children_docs.values().to_array()
}
///|
pub fn Doc::subdoc_guids(self : Doc) -> Set[String] {
Set::from_array(self.subdocs().map(Doc::guid))
}
///|
fn Doc::unloaded_copy(self : Doc) -> Doc {
make_doc(
self.client,
self.document_guid,
self.collection,
self.auto_loading,
false,
self.wire_gc,
self.wire_encoding,
cleanup_enabled=self.cleanup_enabled,
)
}
///|
fn Doc::wire_options(self : Doc) -> Any {
let fields : Array[(String, Any)] = [
("gc", Bool(self.wire_gc)),
("encoding", Integer(self.wire_encoding.to_int64())),
("autoLoad", Bool(self.auto_loading)),
("shouldLoad", Bool(self.loading)),
]
if self.collection is Some(collection) {
fields.push(("collectionId", String(collection)))
}
Object(ReadOnlyArray::from_array(fields[:]))
}
///|
fn decode_subdocument(guid : String, options : Any) -> Doc {
let mut gc = true
let mut encoding = 1
let mut auto_load = false
let mut collection = None
if options is Object(fields) {
for (key, value) in fields {
match (key, value) {
("gc", Bool(value)) => gc = value
("encoding", Integer(1L)) => encoding = 1
("encoding", _) => encoding = 0
("autoLoad", Bool(value)) => auto_load = value
("collectionId", String(value)) => collection = Some(value)
_ => ()
}
}
}
// Like Yrs, receiving shouldLoad does not request a load. autoLoad does.
// The parent assigns its client ID at commit, before providers can edit it.
let child = make_doc(
0UL,
guid,
collection,
auto_load,
auto_load,
gc,
encoding,
cleanup_enabled=false,
)
child.decoded_options = Some(options)
child
}
///|
fn Doc::check_subdocument(self : Doc, child : Doc) -> Unit {
guard child.document_guid != "" else {
abort("subdocuments require an explicit globally unique guid")
}
guard child.owner is None else {
abort("document already belongs to a parent")
}
let mut ancestor = Some(self)
while ancestor is Some(doc) {
guard doc.identity != child.identity else { abort("subdocument cycle") }
ancestor = doc.parent_doc()
}
}
///|
pub fn MapRef::insert_doc(self : MapRef, key : String, child : Doc) -> Doc {
self.doc.check_subdocument(child)
self.doc.insert_content(
self.name,
Some(key),
self.doc.branch(self.name).values.get(key),
None,
Subdocument(child),
)
|> ignore
child
}
///|
pub fn ArrayRef::insert_doc(self : ArrayRef, index : Int32, child : Doc) -> Doc {
self.doc.check_subdocument(child)
let sequence : Text = { doc: self.doc, name: self.name, }
let (left, right) = sequence.position(index)
self.doc.insert_content(self.name, None, left, right, Subdocument(child))
|> ignore
child
}
///|
/// Request loading locally. Providers observe the parent's loaded event and
/// exchange this document's updates separately from the parent's updates.
pub fn Doc::load(self : Doc) -> Unit {
if self.loading {
return
}
self.loading = true
if self.owner is Some((parent, _)) {
parent.with_mutation(fn() {
parent.active_transaction.unwrap().sub_loaded[self.identity] = self
})
}
}
///|
pub fn Doc::observe_subdocs(
self : Doc,
key : String,
callback : (SubdocsEvent, Transaction) -> Unit,
) -> Unit {
self.subdoc_observers.subscribe(key, callback)
}
///|
pub fn Doc::unobserve_subdocs(self : Doc, key : String) -> Bool {
self.subdoc_observers.unsubscribe(key)
}
///|
pub fn Doc::observe_destroy(
self : Doc,
key : String,
callback : (Doc) -> Unit,
) -> Unit {
self.destroy_observers.subscribe(key, callback)
}
///|
pub fn Doc::unobserve_destroy(self : Doc, key : String) -> Bool {
self.destroy_observers.unsubscribe(key)
}
///|
fn Doc::commit_subdocuments(self : Doc, txn : Transaction) -> Unit {
if !txn.sub_changed &&
txn.sub_added.is_empty() &&
txn.sub_removed.is_empty() &&
txn.sub_loaded.is_empty() {
return
}
for child in txn.sub_added.values() {
child.client = self.client
if child.collection is None {
child.collection = self.collection
}
self.children_docs[child.identity] = child
}
for identity in txn.sub_removed.keys() {
self.children_docs.remove(identity)
}
let event : SubdocsEvent = {
additions: ReadOnlyArray::from_array(txn.sub_added.values().to_array()[:]),
removals: ReadOnlyArray::from_array(txn.sub_removed.values().to_array()[:]),
loads: ReadOnlyArray::from_array(txn.sub_loaded.values().to_array()[:]),
}
self.subdoc_observers.trigger(fn(callback) { callback(event, txn) })
// Removal unloads the old handle after observers have inspected it.
for child in event.removals {
child.destroy_contents()
}
}
///|
fn Doc::destroy_contents(self : Doc) -> Unit {
for child in self.subdocs() {
child.destroy_contents()
}
self.children_docs.clear()
if self.owner is Some((parent, id)) &&
parent.items[id].content is Subdocument(current) &&
current.identity == self.identity {
let replacement = self.unloaded_copy()
replacement.owner = Some((parent, id))
parent.items[id].content = Subdocument(replacement)
}
self.owner = None
self.destroy_observers.trigger(fn(callback) { callback(self) })
self.destroy_observers.clear()
self.subdoc_observers.clear()
for observers in self.weak_observers.values() {
observers.clear()
}
self.weak_observers.clear()
for observers in self.deep_observers.values() {
observers.clear()
}
self.deep_observers.clear()
for observers in self.xml_observers.values() {
observers.clear()
}
self.xml_observers.clear()
for observers in self.xml_text_observers.values() {
observers.clear()
}
self.xml_text_observers.clear()
for observers in self.array_observers.values() {
observers.clear()
}
self.array_observers.clear()
for observers in self.text_observers.values() {
observers.clear()
}
self.text_observers.clear()
for observers in self.map_observers.values() {
observers.clear()
}
self.map_observers.clear()
self.before_observers.clear()
self.after_observers.clear()
self.undo_retention.clear()
self.cleanup_observers.clear()
self.update_v1_observers.clear()
self.update_v2_observers.clear()
}
///|
/// Unload this handle. A live parent reference becomes a fresh, unloaded
/// document with the same GUID; persisted subdocument data is not erased.
pub fn Doc::destroy(self : Doc) -> Unit {
if self.owner is Some((parent, id)) {
parent.with_mutation(fn() {
let item = parent.items[id]
let replacement = self.unloaded_copy()
replacement.owner = Some((parent, id))
item.content = Subdocument(replacement)
let txn = parent.active_transaction.unwrap()
// Report replacement as removal plus addition while retaining identity
// separation for documents sharing a GUID.
txn.sub_removed[self.identity] = self
if !item.deleted {
txn.sub_added[replacement.identity] = replacement
}
})
} else {
self.destroy_contents()
}
}
///|
/// Identity of this subdocument's owning item in its parent document.
pub fn Doc::branch_id(self : Doc) -> BranchId? {
self.owner.map(fn(p) { BranchId::Nested(p.1) })
}