@@ -74,9 +75,9 @@
>{@html $cur_tab_persistent_error.desc}
Dismiss
- {:else if $nav_bar.save !== undefined}
+ {:else if $nav_bar_save !== undefined && !$cur_tab_view_or_edit && !$cur_tab_graph_or_discrete}
- {#if $nav_bar.save }
+ {#if $nav_bar_save }
diff --git a/ng-app/src/routes/NURI.svelte b/ng-app/src/routes/NURI.svelte
index 9827ee9..8bdfaf2 100644
--- a/ng-app/src/routes/NURI.svelte
+++ b/ng-app/src/routes/NURI.svelte
@@ -36,7 +36,10 @@
else if (params[1].startsWith("o:"+$active_session.protected_store_id)) push("#/shared");
else if (params[1].startsWith("o:"+$active_session.public_store_id)) push("#/site"); else nuri = params[1]; }
onMount(() => {
- change_nav_bar("nav:unknown_doc",$t("doc.doc"), true);
+ if ($cur_tab.store.store_type)
+ change_nav_bar(`nav:${$cur_tab.store.store_type}`,$t(`doc.${$cur_tab.store.store_type}_store`), true);
+ else
+ change_nav_bar("nav:unknown_doc",$t("doc.doc"), true);
reset_in_memory();
});
diff --git a/ng-app/src/store.ts b/ng-app/src/store.ts
index 672450a..92c9520 100644
--- a/ng-app/src/store.ts
+++ b/ng-app/src/store.ts
@@ -395,6 +395,28 @@ export const digest_to_string = function(digest) {
return encode(buffer.buffer);
};
+export const discrete_update = async (update) => {
+ // if cur_tab.doc.live_edit => send directly to verifier (with live_discrete_update)
+ // else, save the update locally with the API.
+ // and nav_bar.update((o) => { o.save = true; return o; });
+ // once save button is pressed, we call OnSave with all the updates that we retrieve from local storage (via API). and we set nav_bar.update((o) => { o.save = false; return o; });
+ // the editor then process those updates and calls live_discrete_update
+}
+
+export const live_discrete_update = async (update, crdt, heads) => {
+ // send directly to verifier with AppRequest Update
+ let session = get(active_session);
+ if (!session) {
+ persistent_error(get(cur_branch), {
+ title: get(format)("doc.errors.no_session"),
+ desc: get(format)("doc.errors_details.no_session")
+ });
+ throw new Error("no session");
+ }
+ let nuri = "did:ng:"+get(cur_tab).branch.nuri;
+ await ng.discrete_update(session.session_id, update, heads, crdt, nuri);
+}
+
export const sparql_query = async function(sparql:string, union:boolean) {
let session = get(active_session);
if (!session) {
@@ -472,10 +494,12 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
//console.log("sub");
let already_subscribed = all_branches[nuri];
if (!already_subscribed) {
- const { subscribe, set, update } = writable({graph:[], discrete:[], files:[], history: {start:()=>{}, stop:()=>{}, commits:false}, heads: []}); // create the underlying writable store // take:()=>{},
+ let onUpdate = (update) => {};
+ const { subscribe, set, update } = writable({graph:[], discrete:{updates:[], deregisterOnUpdate:()=>{ onUpdate=()=>{};},registerOnUpdate:(f)=>{ }}, files:[], history: {start:()=>{}, stop:()=>{}, commits:false}, heads: []}); // create the underlying writable store // take:()=>{},
update((old)=> {
old.history.start = () => update((o) => {o.history.commits = true; return o;}) ;
old.history.stop = () => update((o) => {o.history.commits = false; return o;}) ;
+ old.discrete.registerOnUpdate = (f) => { onUpdate = f; return get({subscribe}).discrete.updates; };
//old.history.take = () => { let res: boolean | Array<{}> = false; update((o) => {res = o.history.commits; o.history.commits = []; return o;}); return res;}
return old;});
let count = 0;
@@ -562,6 +586,10 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
}
old.graph.sort();
}
+ if (response.V0.State.discrete) {
+ old.discrete.updates.push(response.V0.State.discrete);
+ onUpdate(response.V0.State.discrete);
+ }
tab_update(nuri, ($cur_tab) => {
$cur_tab.branch.files = old.files.length;
return $cur_tab;
@@ -582,6 +610,10 @@ export const branch_subscribe = function(nuri:string, in_tab:boolean) {
old.history.commits.push(commit);
}
}
+ if (response.V0.Patch.discrete) {
+ old.discrete.updates.push(response.V0.Patch.discrete);
+ onUpdate(response.V0.Patch.discrete);
+ }
if (response.V0.Patch.graph) {
let duplicates = [];
for (let i = 0; i < old.graph.length; i++) {
diff --git a/ng-app/src/tab.ts b/ng-app/src/tab.ts
index cb0b6b2..2135c21 100644
--- a/ng-app/src/tab.ts
+++ b/ng-app/src/tab.ts
@@ -260,6 +260,8 @@ export const all_tabs = writable({
description: "",
app: "", // current app being used
+ onSave: (updates) => {},
+ updates: [],
},
view_or_edit: true, // true=> view, false=> edit
graph_viewer: "", // selected viewer
@@ -304,6 +306,13 @@ export const all_tabs = writable({
}
});
+export const cur_tab_register_on_save = (f:(updates) => {}) => {
+ cur_tab_update((old)=>{
+ old.branch.onSave = f;
+ return old;
+ });
+}
+
export const set_header_in_view = function(val) {
cur_tab_update((old) => { old.header_in_view = val; return old;});
}
@@ -427,13 +436,6 @@ export const cur_tab_update = function( fn ) {
export const live_editing = writable(false);
-live_editing.subscribe((val) => {
- cur_tab_update((old)=> {
- old.doc.live_edit = val;
- return old;
- });
-});
-
export const showMenu = () => {
show_modal_menu.set(true);
cur_tab_update(ct => {
@@ -458,13 +460,42 @@ export const nav_bar = writable({
back: false,
newest: 0,
save: undefined,
- toasts: [],
+});
+
+live_editing.subscribe((val) => {
+ cur_tab_update((old)=> {
+ old.doc.live_edit = val;
+ if (val) {
+ //TODO: send all the updates with live_discrete_update
+ }
+ nav_bar.update((o) => {
+ o.save = old.doc.live_edit ? undefined : ( old.branch.updates.length > 0 ? true : false )
+ return o;
+ });
+ return old;
+ });
});
export const nav_bar_newest = derived(nav_bar, ($nav_bar) => {
return $nav_bar.newest;
});
+export const nav_bar_save = derived(nav_bar, ($nav_bar) => {
+ return $nav_bar.save;
+});
+
+export const nav_bar_back = derived(nav_bar, ($nav_bar) => {
+ return $nav_bar.back;
+});
+
+export const nav_bar_title = derived(nav_bar, ($nav_bar) => {
+ return $nav_bar.title;
+});
+
+export const nav_bar_icon = derived(nav_bar, ($nav_bar) => {
+ return $nav_bar.icon;
+});
+
export const nav_bar_reset_newest = () => {
nav_bar.update((old) => {
old.newest = 0;
@@ -496,6 +527,8 @@ export const persistent_error = (nuri, pe) => {
export const save = async () => {
// saving the doc
+ // fetch updates from local storage
+ get(cur_tab).branch.onSave([]);
}
export const all_files_count = derived(cur_tab, ($cur_tab) => {
@@ -515,6 +548,7 @@ export const has_editor_chat = derived(cur_tab, ($cur_tab) => {
export const toggle_live_edit = () => {
cur_tab_update(ct => {
ct.doc.live_edit = !ct.doc.live_edit;
+ live_editing.set(ct.doc.live_edit);
return ct;
});
}
diff --git a/ng-app/src/zeras.ts b/ng-app/src/zeras.ts
index f9918c8..609d15e 100644
--- a/ng-app/src/zeras.ts
+++ b/ng-app/src/zeras.ts
@@ -226,7 +226,7 @@ export const official_apps = {
"ng:w": ["post:md"],
},
"n:g:z:code_editor": {
- "ng:n": "Code and Text Editor",
+ "ng:n": "Text Editor",
"ng:a": "Edit the code/text with CodeMirror",
"ng:c": "app",
"ng:u": "edit",//favicon. can be a did:ng:j
diff --git a/ng-net/src/app_protocol.rs b/ng-net/src/app_protocol.rs
index b553617..b9f61d8 100644
--- a/ng-net/src/app_protocol.rs
+++ b/ng-net/src/app_protocol.rs
@@ -43,12 +43,12 @@ lazy_static! {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum AppFetchContentV0 {
- Get, // does not subscribe. more to be detailed
- Subscribe, // more to be detailed
+ Get, // does not subscribe.
+ Subscribe,
Update,
//Invoke,
- ReadQuery, // more to be detailed
- WriteQuery, // more to be detailed
+ ReadQuery,
+ WriteQuery,
RdfDump,
History,
}
@@ -92,6 +92,13 @@ impl TargetBranchV0 {
_ => true,
}
}
+ pub fn is_valid_for_discrete_update(&self) -> bool {
+ match self {
+ Self::BranchId(_) => true,
+ //TODO: add Named(s) is s is a branch => true
+ _ => false,
+ }
+ }
pub fn branch_id(&self) -> &BranchId {
match self {
Self::BranchId(id) => id,
@@ -124,6 +131,12 @@ impl NuriTargetV0 {
_ => true,
}
}
+ pub fn is_valid_for_discrete_update(&self) -> bool {
+ match self {
+ Self::UserSite | Self::AllDialogs | Self::AllGroups | Self::None => false,
+ _ => true,
+ }
+ }
pub fn is_repo_id(&self) -> bool {
match self {
Self::Repo(_) => true,
@@ -276,6 +289,15 @@ impl NuriV0 {
.as_ref()
.map_or(true, |b| b.is_valid_for_sparql_update())
}
+ pub fn is_valid_for_discrete_update(&self) -> bool {
+ self.object.is_none()
+ && self.entire_store == false
+ && self.target.is_valid_for_discrete_update()
+ && self
+ .branch
+ .as_ref()
+ .map_or(true, |b| b.is_valid_for_discrete_update())
+ }
pub fn new_repo_target_from_string(repo_id_string: String) -> Result
{
let repo_id: RepoId = repo_id_string.as_str().try_into()?;
Ok(Self {
@@ -460,6 +482,9 @@ impl AppRequestCommandV0 {
pub fn new_write_query() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::WriteQuery)
}
+ pub fn new_update() -> Self {
+ AppRequestCommandV0::Fetch(AppFetchContentV0::Update)
+ }
pub fn new_rdf_dump() -> Self {
AppRequestCommandV0::Fetch(AppFetchContentV0::RdfDump)
}
@@ -616,11 +641,24 @@ pub enum DiscreteUpdate {
Automerge(Vec),
}
+impl DiscreteUpdate {
+ pub fn from(crdt: String, update: Vec) -> Self {
+ match crdt.as_str() {
+ "YMap" => Self::YMap(update),
+ "YArray" => Self::YArray(update),
+ "YXml" => Self::YXml(update),
+ "YText" => Self::YText(update),
+ "Automerge" => Self::Automerge(update),
+ _ => panic!("wrong crdt type"),
+ }
+ }
+}
+
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DocUpdate {
- heads: Vec,
- graph: Option,
- discrete: Option,
+ pub heads: Vec,
+ pub graph: Option,
+ pub discrete: Option,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -683,6 +721,24 @@ impl AppRequestPayload {
pub fn new_sparql_query(query: String) -> Self {
AppRequestPayload::V0(AppRequestPayloadV0::Query(DocQuery::V0(query)))
}
+ pub fn new_discrete_update(
+ head_strings: Vec,
+ crdt: String,
+ update: Vec,
+ ) -> Result {
+ let mut heads = Vec::with_capacity(head_strings.len());
+ for head in head_strings {
+ heads.push(decode_digest(&head)?);
+ }
+ let discrete = Some(DiscreteUpdate::from(crdt, update));
+ Ok(AppRequestPayload::V0(AppRequestPayloadV0::Update(
+ DocUpdate {
+ heads,
+ graph: None,
+ discrete,
+ },
+ )))
+ }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -713,7 +769,7 @@ pub struct GraphPatch {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DiscreteState {
- /// A yrs::StateVector
+ /// A yrs::Update
#[serde(with = "serde_bytes")]
YMap(Vec),
#[serde(with = "serde_bytes")]
diff --git a/ng-repo/src/errors.rs b/ng-repo/src/errors.rs
index 9e860ae..9f4743e 100644
--- a/ng-repo/src/errors.rs
+++ b/ng-repo/src/errors.rs
@@ -226,6 +226,7 @@ pub enum StorageError {
NotEmpty,
ServerAlreadyRunningInOtherProcess,
NgError(String),
+ NoDiscreteState,
}
impl core::fmt::Display for StorageError {
@@ -367,6 +368,8 @@ pub enum VerifierError {
OxigraphError(String),
CannotRemoveTriplesWhenNewBranch,
PermissionDenied,
+ YrsError(String),
+ InvalidNuri,
}
impl Error for VerifierError {}
diff --git a/ng-repo/src/types.rs b/ng-repo/src/types.rs
index 16abb96..901c2d5 100644
--- a/ng-repo/src/types.rs
+++ b/ng-repo/src/types.rs
@@ -1366,6 +1366,12 @@ pub enum BranchCrdt {
}
impl BranchCrdt {
+ pub fn is_graph(&self) -> bool {
+ match self {
+ BranchCrdt::Graph(_) => true,
+ _ => false,
+ }
+ }
pub fn name(&self) -> String {
match self {
BranchCrdt::Graph(_) => "Graph",
diff --git a/ng-sdk-js/src/lib.rs b/ng-sdk-js/src/lib.rs
index d6e407a..891636c 100644
--- a/ng-sdk-js/src/lib.rs
+++ b/ng-sdk-js/src/lib.rs
@@ -314,6 +314,49 @@ pub async fn sparql_query(
}
}
+#[wasm_bindgen]
+pub async fn discrete_update(
+ session_id: JsValue,
+ update: JsValue,
+ heads: Array,
+ crdt: String,
+ nuri: String,
+) -> Result<(), String> {
+ let session_id: u64 = serde_wasm_bindgen::from_value::(session_id)
+ .map_err(|_| "Invalid session_id".to_string())?;
+ let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?;
+ let mut head_strings = Vec::with_capacity(heads.length() as usize);
+ for head in heads.iter() {
+ if let Some(s) = head.as_string() {
+ head_strings.push(s)
+ } else {
+ return Err("Invalid HEADS".to_string());
+ }
+ }
+ let update: serde_bytes::ByteBuf =
+ serde_wasm_bindgen::from_value::(update)
+ .map_err(|_| "Deserialization error of update".to_string())?;
+
+ let request = AppRequest::V0(AppRequestV0 {
+ command: AppRequestCommandV0::new_update(),
+ nuri,
+ payload: Some(
+ AppRequestPayload::new_discrete_update(head_strings, crdt, update.into_vec())
+ .map_err(|e| format!("Deserialization error of heads: {e}"))?,
+ ),
+ session_id,
+ });
+
+ let res = nextgraph::local_broker::app_request(request)
+ .await
+ .map_err(|e: NgError| e.to_string())?;
+ if let AppResponse::V0(AppResponseV0::Error(e)) = res {
+ Err(e)
+ } else {
+ Ok(())
+ }
+}
+
#[wasm_bindgen]
pub async fn sparql_update(
session_id: JsValue,
diff --git a/ng-verifier/Cargo.toml b/ng-verifier/Cargo.toml
index 4a1edcb..e3452f0 100644
--- a/ng-verifier/Cargo.toml
+++ b/ng-verifier/Cargo.toml
@@ -30,7 +30,7 @@ futures = "0.3.24"
async-trait = "0.1.64"
async-std = { version = "1.12.0", features = [ "attributes", "unstable" ] }
automerge = "0.5.9"
-yrs = "0.18.2"
+yrs = "0.19.2"
sbbf-rs-safe = "0.3.2"
ng-repo = { path = "../ng-repo", version = "0.1.0-preview.1" }
ng-net = { path = "../ng-net", version = "0.1.0-preview.1" }
diff --git a/ng-verifier/src/commits/mod.rs b/ng-verifier/src/commits/mod.rs
index 120ea80..9f1b120 100644
--- a/ng-verifier/src/commits/mod.rs
+++ b/ng-verifier/src/commits/mod.rs
@@ -574,9 +574,11 @@ impl CommitVerifier for AddRepo {
let remote = (&verifier.connected_broker).into();
let user = Some(verifier.user_id().clone());
let read_cap = self.read_cap();
+ let overlay_id = store.overlay_id;
verifier
.load_repo_from_read_cap(read_cap, &broker, &user, &remote, store, true)
.await?;
+ verifier.add_doc(repo_id, &overlay_id)?;
Ok(())
}
}
diff --git a/ng-verifier/src/commits/transaction.rs b/ng-verifier/src/commits/transaction.rs
index e8d2d8f..c0d42bf 100644
--- a/ng-verifier/src/commits/transaction.rs
+++ b/ng-verifier/src/commits/transaction.rs
@@ -14,7 +14,10 @@ use std::sync::Arc;
use ng_oxigraph::oxigraph::storage_ng::numeric_encoder::{EncodedQuad, EncodedTerm};
use ng_oxigraph::oxigraph::storage_ng::*;
+use ng_repo::repo::Repo;
use serde::{Deserialize, Serialize};
+use yrs::updates::decoder::Decode;
+use yrs::{ReadTxn, StateVector, Transact, Update};
use ng_net::app_protocol::*;
use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
@@ -23,53 +26,9 @@ use ng_repo::log::*;
use ng_repo::store::Store;
use ng_repo::types::*;
+use crate::types::*;
use crate::verifier::Verifier;
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct GraphTransaction {
- pub inserts: Vec,
- pub removes: Vec,
-}
-
-impl GraphTransaction {
- fn as_patch(&self) -> GraphPatch {
- GraphPatch {
- inserts: serde_bare::to_vec(&self.inserts).unwrap(),
- removes: serde_bare::to_vec(&self.removes).unwrap(),
- }
- }
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum DiscreteTransaction {
- /// A yrs::Update
- #[serde(with = "serde_bytes")]
- YMap(Vec),
- #[serde(with = "serde_bytes")]
- YArray(Vec),
- #[serde(with = "serde_bytes")]
- YXml(Vec),
- #[serde(with = "serde_bytes")]
- YText(Vec),
- /// An automerge::Patch
- #[serde(with = "serde_bytes")]
- Automerge(Vec),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum TransactionBodyType {
- Graph,
- Discrete,
- Both,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub struct TransactionBody {
- body_type: TransactionBodyType,
- graph: Option,
- discrete: Option,
-}
-
struct BranchUpdateInfo {
branch_id: BranchId,
branch_is_main: bool,
@@ -208,6 +167,77 @@ impl Verifier {
.map_err(|e| VerifierError::OxigraphError(e.to_string()))
}
+ pub(crate) async fn update_discrete(
+ &mut self,
+ patch: DiscreteTransaction,
+ crdt: &BranchCrdt,
+ branch_id: &BranchId,
+ commit_id: ObjectId,
+ commit_info: CommitInfoJs,
+ ) -> Result<(), VerifierError> {
+ let new_state = if let Ok(state) = self
+ .user_storage
+ .as_ref()
+ .unwrap()
+ .branch_get_discrete_state(branch_id)
+ {
+ match crdt {
+ BranchCrdt::Automerge(_) => {
+ unimplemented!();
+ }
+ BranchCrdt::YArray(_)
+ | BranchCrdt::YMap(_)
+ | BranchCrdt::YText(_)
+ | BranchCrdt::YXml(_) => {
+ let doc = yrs::Doc::new();
+ {
+ let mut txn = doc.transact_mut();
+ let update = yrs::Update::decode_v1(&state)
+ .map_err(|e| VerifierError::YrsError(e.to_string()))?;
+ txn.apply_update(update);
+ let update = yrs::Update::decode_v1(&patch.to_vec())
+ .map_err(|e| VerifierError::YrsError(e.to_string()))?;
+ txn.apply_update(update);
+ txn.commit();
+ }
+ let empty_state_vector = yrs::StateVector::default();
+ let transac = doc.transact();
+ transac.encode_state_as_update_v1(&empty_state_vector)
+ }
+ _ => return Err(VerifierError::InvalidBranch),
+ }
+ } else {
+ patch.to_vec()
+ };
+ self.user_storage
+ .as_ref()
+ .unwrap()
+ .branch_set_discrete_state(*branch_id, new_state)?;
+
+ let patch = match (crdt, patch) {
+ (BranchCrdt::Automerge(_), DiscreteTransaction::Automerge(v)) => {
+ DiscretePatch::Automerge(v)
+ }
+ (BranchCrdt::YArray(_), DiscreteTransaction::YArray(v)) => DiscretePatch::YArray(v),
+ (BranchCrdt::YMap(_), DiscreteTransaction::YMap(v)) => DiscretePatch::YMap(v),
+ (BranchCrdt::YText(_), DiscreteTransaction::YText(v)) => DiscretePatch::YText(v),
+ (BranchCrdt::YXml(_), DiscreteTransaction::YXml(v)) => DiscretePatch::YXml(v),
+ _ => return Err(VerifierError::InvalidCommit),
+ };
+ self.push_app_response(
+ branch_id,
+ AppResponse::V0(AppResponseV0::Patch(AppPatch {
+ commit_id: commit_id.to_string(),
+ commit_info: commit_info,
+ graph: None,
+ discrete: Some(patch),
+ other: None,
+ })),
+ )
+ .await;
+ Ok(())
+ }
+
pub(crate) async fn verify_async_transaction(
&mut self,
transaction: &Transaction,
@@ -239,12 +269,54 @@ impl Verifier {
commit_info,
};
self.update_graph(vec![info]).await?;
+ } else
+ //TODO: change the logic here. transaction commits can have both a discrete and graph update. Only one AppResponse should be sent in this case, containing both updates.
+ if body.discrete.is_some() {
+ let patch = body.discrete.unwrap();
+ let crdt = &repo.branch(branch_id)?.crdt.clone();
+ self.update_discrete(patch, &crdt, branch_id, commit_id, commit_info)
+ .await?;
}
- //TODO: discrete update
Ok(())
}
+ // pub(crate) fn find_branch_and_repo_for_nuri(
+ // &self,
+ // nuri: &NuriV0,
+ // ) -> Result<(RepoId, BranchId, StoreRepo), VerifierError> {
+ // if !nuri.is_branch_identifier() {
+ // return Err(VerifierError::InvalidNuri);
+ // }
+ // let store = self.get_store_by_overlay_id(&OverlayId::Outer(
+ // nuri.overlay.as_ref().unwrap().outer().to_slice(),
+ // ))?;
+ // let repo = self.get_repo(nuri.target.repo_id(), store.get_store_repo())?;
+ // Ok((
+ // match nuri.branch {
+ // None => {
+ // let b = repo.main_branch().ok_or(VerifierError::BranchNotFound)?;
+ // if b.topic_priv_key.is_none() {
+ // return Err(VerifierError::PermissionDenied);
+ // }
+ // b.id
+ // }
+ // Some(TargetBranchV0::BranchId(id)) => {
+ // let b = repo.branch(&id)?;
+ // //TODO: deal with named branch that is also the main branch
+ // if b.topic_priv_key.is_none() {
+ // return Err(VerifierError::PermissionDenied);
+ // }
+ // id
+ // }
+ // // TODO: implement TargetBranchV0::Named
+ // _ => unimplemented!(),
+ // },
+ // repo.id,
+ // store.get_store_repo().clone(),
+ // ))
+ // }
+
fn find_branch_and_repo_for_quad(
&self,
quad: &Quad,
diff --git a/ng-verifier/src/request_processor.rs b/ng-verifier/src/request_processor.rs
index 7aa6445..43a0197 100644
--- a/ng-verifier/src/request_processor.rs
+++ b/ng-verifier/src/request_processor.rs
@@ -266,12 +266,11 @@ impl Verifier {
.await?;
// adding an ldp:contains triple to the store main branch
- let nuri = NuriV0::repo_graph_name(&repo_id, &doc_create.store.outer_overlay());
+ let overlay_id = doc_create.store.outer_overlay();
+ let nuri = NuriV0::repo_graph_name(&repo_id, &overlay_id);
let store_nuri = NuriV0::from_store_repo(&doc_create.store);
- let store_nuri_string = NuriV0::repo_graph_name(
- doc_create.store.repo_id(),
- &doc_create.store.outer_overlay(),
- );
+ let store_nuri_string =
+ NuriV0::repo_graph_name(doc_create.store.repo_id(), &overlay_id);
let query = format!("INSERT DATA {{ <{store_nuri_string}> <{nuri}>. }}");
let ret = self.process_sparql_update(&store_nuri, &query).await;
@@ -279,6 +278,8 @@ impl Verifier {
return Ok(AppResponse::error(e));
}
+ self.add_doc(&repo_id, &overlay_id)?;
+
return Ok(AppResponse::V0(AppResponseV0::Nuri(nuri)));
} else {
return Err(NgError::InvalidPayload);
@@ -333,6 +334,64 @@ impl Verifier {
Err(NgError::InvalidPayload)
};
}
+ AppFetchContentV0::Update => {
+ if !nuri.is_valid_for_discrete_update() {
+ return Err(NgError::InvalidNuri);
+ }
+ return if let Some(AppRequestPayload::V0(AppRequestPayloadV0::Update(update))) =
+ payload
+ {
+ //TODO: deal with update.graph
+ //TODO: verify that update.heads are the same as what the Verifier knows
+ if let Some(discrete) = update.discrete {
+ let (repo_id, branch_id, store_repo) =
+ match self.resolve_target(&nuri.target) {
+ Err(e) => return Ok(AppResponse::error(e.to_string())),
+ Ok(a) => a,
+ };
+
+ let patch: DiscreteTransaction = discrete.into();
+
+ let transac = TransactionBody {
+ body_type: TransactionBodyType::Discrete,
+ graph: None,
+ discrete: Some(patch.clone()),
+ };
+
+ let transaction_commit_body = CommitBodyV0::AsyncTransaction(
+ Transaction::V0(serde_bare::to_vec(&transac)?),
+ );
+
+ let commit = self
+ .new_transaction_commit(
+ transaction_commit_body,
+ &repo_id,
+ &branch_id,
+ &store_repo,
+ vec![], //TODO deps
+ vec![],
+ )
+ .await?;
+
+ let repo = self.get_repo(&repo_id, &store_repo)?;
+ let commit_info: CommitInfoJs = (&commit.as_info(repo)).into();
+
+ let crdt = &repo.branch(&branch_id)?.crdt.clone();
+ self.update_discrete(
+ patch,
+ &crdt,
+ &branch_id,
+ commit.id().unwrap(),
+ commit_info,
+ )
+ .await?;
+ }
+
+ Ok(AppResponse::ok())
+ } else {
+ Err(NgError::InvalidPayload)
+ };
+ }
AppFetchContentV0::RdfDump => {
let store = self.graph_dataset.as_ref().unwrap();
diff --git a/ng-verifier/src/rocksdb_user_storage.rs b/ng-verifier/src/rocksdb_user_storage.rs
index 115f982..a514c09 100644
--- a/ng-verifier/src/rocksdb_user_storage.rs
+++ b/ng-verifier/src/rocksdb_user_storage.rs
@@ -95,6 +95,22 @@ impl UserStorage for RocksDbUserStorage {
}
}
+ fn branch_set_discrete_state(
+ &self,
+ branch: BranchId,
+ state: Vec,
+ ) -> Result<(), StorageError> {
+ let branch = BranchStorage::open(&branch, &self.user_storage)?;
+ branch.set_discrete_state(state)
+ }
+
+ fn branch_get_discrete_state(&self, branch: &BranchId) -> Result, StorageError> {
+ let branch = BranchStorage::new(&branch, &self.user_storage)?;
+ branch
+ .get_discrete_state()
+ .map_err(|_| StorageError::NoDiscreteState)
+ }
+
fn branch_add_file(
&self,
commit_id: ObjectId,
diff --git a/ng-verifier/src/types.rs b/ng-verifier/src/types.rs
index 6de872c..2e1f6a4 100644
--- a/ng-verifier/src/types.rs
+++ b/ng-verifier/src/types.rs
@@ -18,9 +18,78 @@ use serde::{Deserialize, Serialize};
//use oxigraph::model::GroundQuad;
//use yrs::{StateVector, Update};
+use ng_net::{app_protocol::*, types::*};
+use ng_oxigraph::oxrdf::{GraphName, GraphNameRef, NamedNode, Quad, Triple, TripleRef};
use ng_repo::{errors::*, types::*};
-use ng_net::types::*;
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct GraphTransaction {
+ pub inserts: Vec,
+ pub removes: Vec,
+}
+
+impl GraphTransaction {
+ pub(crate) fn as_patch(&self) -> GraphPatch {
+ GraphPatch {
+ inserts: serde_bare::to_vec(&self.inserts).unwrap(),
+ removes: serde_bare::to_vec(&self.removes).unwrap(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum DiscreteTransaction {
+ /// A serialization of a yrs::Update
+ #[serde(with = "serde_bytes")]
+ YMap(Vec),
+ #[serde(with = "serde_bytes")]
+ YArray(Vec),
+ #[serde(with = "serde_bytes")]
+ YXml(Vec),
+ #[serde(with = "serde_bytes")]
+ YText(Vec),
+ /// An automerge::Patch
+ #[serde(with = "serde_bytes")]
+ Automerge(Vec),
+}
+
+impl From for DiscreteTransaction {
+ fn from(update: DiscreteUpdate) -> Self {
+ match update {
+ DiscreteUpdate::Automerge(v) => DiscreteTransaction::Automerge(v),
+ DiscreteUpdate::YMap(v) => DiscreteTransaction::YMap(v),
+ DiscreteUpdate::YArray(v) => DiscreteTransaction::YArray(v),
+ DiscreteUpdate::YXml(v) => DiscreteTransaction::YXml(v),
+ DiscreteUpdate::YText(v) => DiscreteTransaction::YText(v),
+ }
+ }
+}
+
+impl DiscreteTransaction {
+ pub fn to_vec(&self) -> Vec {
+ match self {
+ Self::YMap(v)
+ | Self::YArray(v)
+ | Self::YXml(v)
+ | Self::YText(v)
+ | Self::Automerge(v) => v.to_vec(),
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub enum TransactionBodyType {
+ Graph,
+ Discrete,
+ Both,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+pub struct TransactionBody {
+ pub body_type: TransactionBodyType,
+ pub graph: Option,
+ pub discrete: Option,
+}
#[doc(hidden)]
#[derive(Clone, Debug, Serialize, Deserialize)]
diff --git a/ng-verifier/src/user_storage/branch.rs b/ng-verifier/src/user_storage/branch.rs
index 6b663e9..d0e1540 100644
--- a/ng-verifier/src/user_storage/branch.rs
+++ b/ng-verifier/src/user_storage/branch.rs
@@ -42,8 +42,9 @@ impl<'a> BranchStorage<'a> {
const MERGED_IN: u8 = b'm';
const CRDT: u8 = b'd';
const CLASS: u8 = b'c';
+ const DISCRETE_STATE: u8 = b's';
- const ALL_PROPERTIES: [u8; 9] = [
+ const ALL_PROPERTIES: [u8; 10] = [
Self::TYPE,
Self::PUBLISHER,
Self::READ_CAP,
@@ -53,6 +54,7 @@ impl<'a> BranchStorage<'a> {
Self::MERGED_IN,
Self::CRDT,
Self::CLASS,
+ Self::DISCRETE_STATE,
];
const PREFIX_HEADS: u8 = b'h';
@@ -220,6 +222,26 @@ impl<'a> BranchStorage<'a> {
Ok(res)
}
+ pub fn set_discrete_state(&self, state: Vec) -> Result<(), StorageError> {
+ self.storage.write_transaction(&mut |tx| {
+ let id_ser = &to_vec(&self.id)?;
+ tx.put(
+ Self::PREFIX,
+ &id_ser,
+ Some(Self::DISCRETE_STATE),
+ &state,
+ &None,
+ )?;
+ Ok(())
+ })
+ }
+
+ pub fn get_discrete_state(&self) -> Result, StorageError> {
+ let id_ser = &to_vec(&self.id)?;
+ self.storage
+ .get(Self::PREFIX, &id_ser, Some(Self::DISCRETE_STATE), &None)
+ }
+
pub fn add_file(&self, commit_id: &ObjectId, file: &FileName) -> Result<(), StorageError> {
self.storage.write_transaction(&mut |tx| {
let branch_id_ser = to_vec(&self.id)?;
diff --git a/ng-verifier/src/user_storage/storage.rs b/ng-verifier/src/user_storage/storage.rs
index 8fa8d5c..5b20556 100644
--- a/ng-verifier/src/user_storage/storage.rs
+++ b/ng-verifier/src/user_storage/storage.rs
@@ -51,6 +51,14 @@ pub trait UserStorage: Send + Sync {
fn branch_get_all_files(&self, branch: &BranchId) -> Result, StorageError>;
+ fn branch_set_discrete_state(
+ &self,
+ branch: BranchId,
+ state: Vec,
+ ) -> Result<(), StorageError>;
+
+ fn branch_get_discrete_state(&self, branch: &BranchId) -> Result, StorageError>;
+
fn branch_get_tab_info(
&self,
branch: &BranchId,
@@ -68,12 +76,14 @@ pub trait UserStorage: Send + Sync {
pub(crate) struct InMemoryUserStorage {
branch_files: RwLock>>,
+ branch_discrete_state: RwLock>>,
}
impl InMemoryUserStorage {
pub fn new() -> Self {
InMemoryUserStorage {
branch_files: RwLock::new(HashMap::new()),
+ branch_discrete_state: RwLock::new(HashMap::new()),
}
}
}
@@ -100,6 +110,25 @@ impl UserStorage for InMemoryUserStorage {
}
}
+ fn branch_set_discrete_state(
+ &self,
+ branch: BranchId,
+ state: Vec,
+ ) -> Result<(), StorageError> {
+ let mut lock = self.branch_discrete_state.write().unwrap();
+ let _ = lock.insert(branch, state);
+ Ok(())
+ }
+
+ fn branch_get_discrete_state(&self, branch: &BranchId) -> Result, StorageError> {
+ let lock = self.branch_discrete_state.read().unwrap();
+ if let Some(state) = lock.get(&branch) {
+ Ok(state.to_vec())
+ } else {
+ Err(StorageError::NoDiscreteState)
+ }
+ }
+
fn branch_get_tab_info(
&self,
branch: &BranchId,
diff --git a/ng-verifier/src/verifier.rs b/ng-verifier/src/verifier.rs
index 87b1eb1..e736d42 100644
--- a/ng-verifier/src/verifier.rs
+++ b/ng-verifier/src/verifier.rs
@@ -261,7 +261,7 @@ impl Verifier {
let store_tab_info = AppTabStoreInfo {
repo: Some(repo.store.get_store_repo().clone()),
- overlay: Some(format!("v:{}", repo.store.outer_overlay().to_string())),
+ overlay: Some(format!("v:{}", repo.store.overlay_id.to_string())),
store_type: Some(repo.store.get_store_repo().store_type_for_app()),
has_outer: None, //TODO
inner: None, //TODO
@@ -335,6 +335,29 @@ impl Verifier {
}
}
+ let crdt = &repo.branch(&branch_id)?.crdt;
+ let discrete = if crdt.is_graph() {
+ None
+ } else {
+ match self
+ .user_storage
+ .as_ref()
+ .unwrap()
+ .branch_get_discrete_state(&branch_id)
+ {
+ Ok(state) => Some(match repo.branch(&branch_id)?.crdt {
+ BranchCrdt::Automerge(_) => DiscreteState::Automerge(state),
+ BranchCrdt::YArray(_) => DiscreteState::YArray(state),
+ BranchCrdt::YMap(_) => DiscreteState::YMap(state),
+ BranchCrdt::YText(_) => DiscreteState::YText(state),
+ BranchCrdt::YXml(_) => DiscreteState::YXml(state),
+ _ => return Err(VerifierError::InvalidBranch),
+ }),
+ Err(StorageError::NoDiscreteState) => None,
+ Err(e) => return Err(e.into()),
+ }
+ };
+
let state = AppState {
heads: branch.current_heads.iter().map(|h| h.id.clone()).collect(),
graph: if results.is_empty() {
@@ -344,7 +367,7 @@ impl Verifier {
triples: serde_bare::to_vec(&results).unwrap(),
})
},
- discrete: None,
+ discrete,
files,
};
@@ -2424,7 +2447,7 @@ impl Verifier {
//TODO: improve the inner_to_outer insert. (should be done when store is created, not here. should work also for dialogs.)
self.inner_to_outer.insert(
repo.store.overlay_for_read_on_client_protocol(),
- repo.store.outer_overlay(),
+ repo.store.overlay_id,
);
for sub in opened_repo {
Self::branch_was_opened(&self.topics, repo, sub)?;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9e09bd4..6a2b467 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -33,6 +33,7 @@ importers:
specifiers:
'@codemirror/autocomplete': ^6.17.0
'@codemirror/commands': ^6.6.0
+ '@codemirror/lang-javascript': ^6.2.2
'@codemirror/language': ^6.10.2
'@codemirror/legacy-modes': ^6.4.0
'@codemirror/lint': ^6.8.1
@@ -81,9 +82,12 @@ importers:
vite-plugin-svelte-svg: ^2.2.1
vite-plugin-top-level-await: ^1.3.1
vite-plugin-wasm: ^3.2.2
+ y-codemirror.next: ^0.3.5
+ yjs: ^13.6.18
dependencies:
'@codemirror/autocomplete': 6.17.0_77urojsfbrmvdrcps23icldzhi
'@codemirror/commands': 6.6.0
+ '@codemirror/lang-javascript': 6.2.2
'@codemirror/language': 6.10.2
'@codemirror/legacy-modes': 6.4.0
'@codemirror/lint': 6.8.1
@@ -106,6 +110,8 @@ importers:
svelte-inview: 4.0.2_svelte@3.59.1
svelte-spa-router: 3.3.0
vite-plugin-top-level-await: 1.3.1_vite@4.3.9
+ y-codemirror.next: 0.3.5_2derscuhaavtzv2sogf3enfvaa
+ yjs: 13.6.18
devDependencies:
'@sveltejs/vite-plugin-svelte': 2.4.1_svelte@3.59.1+vite@4.3.9
'@tauri-apps/cli': 2.0.0-alpha.14
@@ -195,6 +201,18 @@ packages:
'@lezer/common': 1.2.1
dev: false
+ /@codemirror/lang-javascript/6.2.2:
+ resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==}
+ dependencies:
+ '@codemirror/autocomplete': 6.17.0_77urojsfbrmvdrcps23icldzhi
+ '@codemirror/language': 6.10.2
+ '@codemirror/lint': 6.8.1
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.29.1
+ '@lezer/common': 1.2.1
+ '@lezer/javascript': 1.4.17
+ dev: false
+
/@codemirror/language/6.10.2:
resolution: {integrity: sha512-kgbTYTo0Au6dCSc/TFy7fK3fpJmgHDv1sG1KNQKJXVi+xBTEeBPY/M30YXiU6mMXeH+YIDLsbrT4ZwNRdtF+SA==}
dependencies:
@@ -718,6 +736,14 @@ packages:
'@lezer/common': 1.2.1
dev: false
+ /@lezer/javascript/1.4.17:
+ resolution: {integrity: sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==}
+ dependencies:
+ '@lezer/common': 1.2.1
+ '@lezer/highlight': 1.2.0
+ '@lezer/lr': 1.4.1
+ dev: false
+
/@lezer/lr/1.4.1:
resolution: {integrity: sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==}
dependencies:
@@ -1756,6 +1782,10 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
+ /isomorphic.js/0.2.5:
+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+ dev: false
+
/jiti/1.18.2:
resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==}
hasBin: true
@@ -1766,6 +1796,14 @@ packages:
engines: {node: '>=6'}
dev: true
+ /lib0/0.2.95:
+ resolution: {integrity: sha512-St5XGDh5omvNawGkAOa7CFRjxl4xEKLj9DxgT8Nl7rmrD6l2WRTngvmZGhJKRaniROterT0RDVdnwLlU9PiEOg==}
+ engines: {node: '>=16'}
+ hasBin: true
+ dependencies:
+ isomorphic.js: 0.2.5
+ dev: false
+
/lilconfig/2.1.0:
resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==}
engines: {node: '>=10'}
@@ -2770,7 +2808,27 @@ packages:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
+ /y-codemirror.next/0.3.5_2derscuhaavtzv2sogf3enfvaa:
+ resolution: {integrity: sha512-VluNu3e5HfEXybnypnsGwKAj+fKLd4iAnR7JuX1Sfyydmn1jCBS5wwEL/uS04Ch2ib0DnMAOF6ZRR/8kK3wyGw==}
+ peerDependencies:
+ '@codemirror/state': ^6.0.0
+ '@codemirror/view': ^6.0.0
+ yjs: ^13.5.6
+ dependencies:
+ '@codemirror/state': 6.4.1
+ '@codemirror/view': 6.29.1
+ lib0: 0.2.95
+ yjs: 13.6.18
+ dev: false
+
/yaml/2.3.1:
resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
engines: {node: '>= 14'}
dev: true
+
+ /yjs/13.6.18:
+ resolution: {integrity: sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+ dependencies:
+ lib0: 0.2.95
+ dev: false