Skip to content

Commit

Permalink
Merge pull request #2 from mattyg/feat/signal-short-term-updates
Browse files Browse the repository at this point in the history
Feat/signal short term updates
  • Loading branch information
mattyg authored Aug 9, 2023
2 parents 6814ac9 + 9d36a25 commit b6525f1
Show file tree
Hide file tree
Showing 29 changed files with 2,242 additions and 12,729 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules
.direnv
dist
.cargo
target
target
tsconfig.tsbuildinfo
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Yjs already offers bindings for many popular rich text editors including [Quill]

```js
import { HolochainProvider } from 'y-holochain'
import { onMounted, onUnmounted } from 'vue';

let provider: HolochainProvider | undefined;
async setupYjsProvider() {
// Create a document where this Yjs data will be stored
const record = await client.callZome({
Expand All @@ -31,7 +33,7 @@ async setupYjsProvider() {

// Setup Yjs Doc & HolochainProvider
const ydoc = new Y.Doc();
const provider = new HolochainProvider(
provider = new HolochainProvider(
ydoc, // Yjs Y.Doc
client, // Holochain client
"demo", // RoleName of cell with 'yjs' zome
Expand All @@ -43,6 +45,17 @@ async setupYjsProvider() {
const quill = new Quill("#my-editor-container");
new QuillBinding(ydoc.getText('quill'), editor);
}

onMounted(() => {
setupYjsProvider();
});

// Call destroy() when provider no longer in use
onUnmounted(() => {
if(provider) {
provider.destroy()
}
});
```

YJS has bindings for many popular text & rich-text editors. See https://docs.yjs.dev/ecosystem/editor-bindings for details.
9 changes: 3 additions & 6 deletions crates/hc_zome_yjs_coordinator/src/all_documents.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
use hdk::prelude::*;
use hc_zome_yjs_integrity::*;
use hdk::prelude::*;
#[hdk_extern]
pub fn get_all_documents(_: ()) -> ExternResult<Vec<Record>> {
let path = Path::from("all_documents");
let links = get_links(path.path_entry_hash()?, LinkTypes::AllDocuments, None)?;
let get_input: Vec<GetInput> = links
.into_iter()
.filter_map(|link| AnyDhtHash::try_from(link.target).ok())
.map(|hash| GetInput::new(
hash,
GetOptions::default(),
))
.map(|hash| GetInput::new(hash, GetOptions::default()))
.collect();
let records = HDK.with(|hdk| hdk.borrow().get(get_input))?;
let records: Vec<Record> = records.into_iter().filter_map(|r| r).collect();
let records: Vec<Record> = records.into_iter().flatten().collect();
Ok(records)
}
30 changes: 12 additions & 18 deletions crates/hc_zome_yjs_coordinator/src/document.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
use hdk::prelude::*;
use hc_zome_yjs_integrity::*;
use hdk::prelude::*;
#[hdk_extern]
pub fn create_document(document: Document) -> ExternResult<Record> {
let document_hash = create_entry(&EntryTypes::Document(document.clone()))?;
let record = get(document_hash.clone(), GetOptions::default())?
.ok_or(
wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created Document"))
),
)?;
let document_hash = create_entry(&EntryTypes::Document(document))?;
let record = get(document_hash.clone(), GetOptions::default())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created Document"))
))?;
let path = Path::from("all_documents");
create_link(
path.path_entry_hash()?,
document_hash.clone(),
document_hash,
LinkTypes::AllDocuments,
(),
)?;
Expand All @@ -29,8 +26,8 @@ pub fn get_document(original_document_hash: ActionHash) -> ExternResult<Option<R
.into_iter()
.max_by(|link_a, link_b| link_a.timestamp.cmp(&link_b.timestamp));
let latest_document_hash = match latest_link {
Some(link) => ActionHash::try_from(link.target.clone()).map_err(|e| wasm_error!(e))?,
None => original_document_hash.clone(),
Some(link) => ActionHash::try_from(link.target).map_err(|e| wasm_error!(e))?,
None => original_document_hash,
};
get(latest_document_hash, GetOptions::default())
}
Expand All @@ -47,17 +44,14 @@ pub fn update_document(input: UpdateDocumentInput) -> ExternResult<Record> {
&input.updated_document,
)?;
create_link(
input.original_document_hash.clone(),
input.original_document_hash,
updated_document_hash.clone(),
LinkTypes::DocumentUpdates,
(),
)?;
let record = get(updated_document_hash.clone(), GetOptions::default())?
.ok_or(
wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly updated Document"))
),
)?;
let record = get(updated_document_hash, GetOptions::default())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly updated Document"))
))?;
Ok(record)
}
#[hdk_extern]
Expand Down
75 changes: 75 additions & 0 deletions crates/hc_zome_yjs_coordinator/src/document_to_agents.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use hc_zome_yjs_integrity::*;
use hdk::prelude::*;

#[derive(Serialize, Deserialize, SerializedBytes, Debug, Clone)]
pub struct AddAgentForDocumentInput {
pub base_document_hash: ActionHash,
pub target_agent: AgentPubKey,
}
#[hdk_extern]
pub fn add_agent_for_document(input: AddAgentForDocumentInput) -> ExternResult<()> {
create_link(
input.base_document_hash,
input.target_agent,
LinkTypes::DocumentToAgents,
(),
)?;

Ok(())
}

#[hdk_extern]
pub fn remove_agent_for_document(input: AddAgentForDocumentInput) -> ExternResult<()> {
let links = get_links(input.base_document_hash, LinkTypes::DocumentToAgents, None)?;

let agent_links: Vec<Link> = links
.into_iter()
.filter(|link| {
AgentPubKey::try_from(link.target.clone()).ok().unwrap() == input.target_agent
})
.collect();

for link in agent_links {
delete_link(link.create_link_hash)?;
}

Ok(())
}

#[hdk_extern]
pub fn get_agents_for_document(document_hash: ActionHash) -> ExternResult<Vec<AgentPubKey>> {
let links = get_links(document_hash, LinkTypes::DocumentToAgents, None)?;

let agents: Vec<AgentPubKey> = links
.into_iter()
.filter_map(|link| AgentPubKey::try_from(link.target).ok())
.collect();

Ok(agents)
}

#[hdk_extern]
pub fn get_other_agents_for_document(document_hash: ActionHash) -> ExternResult<Vec<AgentPubKey>> {
let mypubkey = agent_info()?.agent_initial_pubkey;
let agents = get_agents_for_document(document_hash)?
.into_iter()
.filter(|agent| agent != &mypubkey)
.collect();

Ok(agents)
}

#[hdk_extern]
pub fn ensure_agent_for_document(input: AddAgentForDocumentInput) -> ExternResult<()> {
let agents = get_agents_for_document(input.clone().base_document_hash)?;

let has_agent = agents
.into_iter()
.any(|agent| agent == input.clone().target_agent);

if !has_agent {
add_agent_for_document(input)?;
}

Ok(())
}
107 changes: 91 additions & 16 deletions crates/hc_zome_yjs_coordinator/src/document_to_statevectors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use hdk::prelude::*;
use crate::document_to_agents::*;
use crate::statevector::*;
use crate::utils::*;
use hc_zome_yjs_integrity::*;
use hdk::prelude::*;

#[derive(Serialize, Deserialize, SerializedBytes, Debug)]
pub struct AddStatevectorForDocumentInput {
Expand All @@ -8,26 +11,31 @@ pub struct AddStatevectorForDocumentInput {
}
#[hdk_extern]
pub fn add_statevector_for_document(input: AddStatevectorForDocumentInput) -> ExternResult<()> {
create_link(input.base_document_hash.clone(), input.target_statevector_hash.clone(), LinkTypes::DocumentToStatevectors, ())?;

create_link_relaxed(
input.base_document_hash,
input.target_statevector_hash,
LinkTypes::DocumentToStatevectors,
(),
)?;

Ok(())
Ok(())
}

#[hdk_extern]
pub fn get_statevectors_for_document(document_hash: ActionHash) -> ExternResult<Vec<Record>> {
let links = get_links(document_hash, LinkTypes::DocumentToStatevectors, None)?;

let get_input: Vec<GetInput> = links
.into_iter()
.filter_map(|link| AnyDhtHash::try_from(link.target).ok())
.map(|hash| GetInput::new(hash, GetOptions::default()))
.collect();

// Get the records to filter out the deleted ones
let records: Vec<Record> = HDK.with(|hdk| hdk.borrow().get(get_input))?
let records: Vec<Record> = HDK
.with(|hdk| hdk.borrow().get(get_input))?
.into_iter()
.filter_map(|r| r)
.flatten()
.collect();

Ok(records)
Expand All @@ -36,23 +44,90 @@ pub fn get_statevectors_for_document(document_hash: ActionHash) -> ExternResult<
#[derive(Serialize, Deserialize, SerializedBytes, Debug)]
pub struct GetStatevectorsForDocumentDelta {
pub document_hash: ActionHash,
pub statevectors: Vec<Statevector>
pub statevectors: Vec<Statevector>,
}
#[hdk_extern]
pub fn get_statevectors_for_document_delta(input: GetStatevectorsForDocumentDelta) -> ExternResult<Vec<Statevector>> {
pub fn get_statevectors_for_document_delta(
input: GetStatevectorsForDocumentDelta,
) -> ExternResult<Vec<Statevector>> {
let all_statevectors = get_statevectors_for_document(input.document_hash)?;
let all_statevectors_btreeset: BTreeSet<Statevector> = BTreeSet::from_iter(
all_statevectors
.iter()
.filter_map(|r| r.entry()
.to_app_option::<Statevector>()
.ok()
)
.filter_map(|s| s)
.filter_map(|r| r.entry().to_app_option::<Statevector>().ok())
.flatten(),
);

let seen_statevectors_btreeset = BTreeSet::from_iter(input.statevectors.iter().cloned());
let new_statevectors = all_statevectors_btreeset.difference(&seen_statevectors_btreeset).cloned().collect();
let new_statevectors = all_statevectors_btreeset
.difference(&seen_statevectors_btreeset)
.cloned()
.collect();

Ok(new_statevectors)
}
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CreateStatevectorForDocumentInput {
pub document_hash: ActionHash,
pub statevector: Statevector,
}
#[hdk_extern]
pub fn create_statevector_for_document(
input: CreateStatevectorForDocumentInput,
) -> ExternResult<Record> {
let sv = create_statevector(input.statevector)?;
if let Some(entry_data) = sv.action().entry_data() {
add_statevector_for_document(AddStatevectorForDocumentInput {
base_document_hash: input.document_hash,
target_statevector_hash: entry_data.0.clone(),
})?;
}

Ok(sv)
}


#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CreateStatevectorForDocumentSignal {
pub provenance: AgentPubKey,
pub document_hash: ActionHash,
pub statevector: Statevector,
}
#[hdk_extern]
pub fn remote_signal_statevector_for_document(
input: CreateStatevectorForDocumentInput,
) -> ExternResult<()> {
let mypubkey = agent_info()?.agent_initial_pubkey;
let agents = get_other_agents_for_document(input.clone().document_hash)?;
remote_signal(CreateStatevectorForDocumentSignal {
provenance: mypubkey,
document_hash: input.document_hash,
statevector: input.statevector
}, agents)?;

Ok(())
}

#[hdk_extern]
pub fn create_or_signal_statevector_for_document(
input: CreateStatevectorForDocumentInput,
) -> ExternResult<()> {
let mut links = get_links(
input.document_hash.clone(),
LinkTypes::DocumentToStatevectors,
None,
)?;
links.sort_by_key(|l| l.timestamp);
let maybe_newest_link = links.last();

// If > 5 mins since last commit, publish commit
if let Some(newest_link) = maybe_newest_link {
if newest_link.timestamp.0 > sys_time()?.0 + (1000 * 60 * 5) {
create_statevector_for_document(input.clone())?;
}
}

// Always remote_signal_sv
remote_signal_statevector_for_document(input)
}
Loading

0 comments on commit b6525f1

Please sign in to comment.