ORM APIs for JS SDK

feat/orm
Niko PLP 4 weeks ago
parent ea6a860c3e
commit 83e07344a1
  1. 3
      nextgraph/src/lib.rs
  2. 39
      ng-net/src/app_protocol.rs
  3. 2
      ng-net/src/lib.rs
  4. 24
      ng-net/src/orm.rs
  5. 5
      ng-verifier/src/commits/transaction.rs
  6. 2
      ng-verifier/src/lib.rs
  7. 144
      ng-verifier/src/orm.rs
  8. 17
      ng-verifier/src/request_processor.rs
  9. 11
      ng-verifier/src/types.rs
  10. 5
      ng-verifier/src/verifier.rs
  11. 2
      pnpm-lock.yaml
  12. 24
      sdk/ng-sdk-js/examples/multi-framework-signals/src/ng-mock/wasm-land/shapeHandler.ts
  13. 40
      sdk/ng-sdk-js/src/lib.rs

@ -93,6 +93,9 @@ pub mod verifier {
pub mod protocol {
pub use ng_net::app_protocol::*;
}
pub mod orm {
pub use ng_verifier::orm::*;
}
pub use ng_verifier::prepare_app_response_for_js;
pub use ng_verifier::read_triples_in_app_response_from_rust;
pub use ng_verifier::triples_ser_to_json_string;

@ -18,7 +18,9 @@ use ng_repo::repo::CommitInfo;
use ng_repo::types::*;
use ng_repo::utils::{decode_digest, decode_key, decode_sym_key};
use ng_repo::utils::{decode_overlayid, display_timestamp_local};
use serde_json::Value;
use crate::orm::{OrmDiff, OrmShapeType};
use crate::types::*;
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -48,7 +50,7 @@ impl AppFetchContentV0 {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum NgAccessV0 {
ReadCap(ReadCap),
Token(Digest),
@ -59,7 +61,7 @@ pub enum NgAccessV0 {
Topic(PrivKey),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum TargetBranchV0 {
Chat,
Stream,
@ -93,7 +95,7 @@ impl TargetBranchV0 {
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub enum NuriTargetV0 {
UserSite, // targets the whole data set of the user
@ -175,7 +177,7 @@ impl From<&CommitInfo> for CommitInfoJs {
const DID_PREFIX: &str = "did:ng";
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct NuriV0 {
pub identity: Option<UserId>, // None for personal identity
pub target: NuriTargetV0,
@ -711,12 +713,15 @@ pub enum AppRequestCommandV0 {
SocialQueryCancel,
QrCodeProfile,
QrCodeProfileImport,
OrmStart,
OrmUpdate,
OrmStop,
}
impl AppRequestCommandV0 {
pub fn is_stream(&self) -> bool {
match self {
Self::Fetch(AppFetchContentV0::Subscribe) | Self::FileGet => true,
Self::Fetch(AppFetchContentV0::Subscribe) | Self::FileGet | Self::OrmStart => true,
_ => false,
}
}
@ -806,6 +811,24 @@ impl AppRequest {
})
}
pub fn new_orm_start(scope: NuriV0, shape_type: OrmShapeType) -> Self {
AppRequest::new(
AppRequestCommandV0::OrmStart,
scope,
Some(AppRequestPayload::V0(AppRequestPayloadV0::OrmStart(shape_type))),
)
}
pub fn new_orm_update(scope: NuriV0, shape_type_name: String, diff: OrmDiff) -> Self {
AppRequest::new(
AppRequestCommandV0::OrmUpdate,
scope,
Some(AppRequestPayload::V0(AppRequestPayloadV0::OrmUpdate((diff,shape_type_name)))),
)
}
pub fn inbox_post(post: InboxPost) -> Self {
AppRequest::new(
AppRequestCommandV0::InboxPost,
@ -1039,6 +1062,9 @@ pub enum AppRequestPayloadV0 {
//Invoke(InvokeArguments),
QrCodeProfile(u32),
QrCodeProfileImport(String),
OrmStart(OrmShapeType),
OrmUpdate((OrmDiff, String)), // ShapeID
OrmStop(String), //ShapeID
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -1288,6 +1314,9 @@ pub enum AppResponseV0 {
Nuri(String),
Header(AppHeader),
Commits(Vec<String>),
OrmInitial(Value),
OrmUpdate(OrmDiff),
OrmError(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]

@ -16,6 +16,8 @@ pub mod app_protocol;
pub mod broker;
pub mod orm;
pub mod server_broker;
#[doc(hidden)]

@ -0,0 +1,24 @@
/*
* Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
* All rights reserved.
* Licensed under the Apache License, Version 2.0
* <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
* or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
* at your option. All files in the project carrying such
* notice may not be copied, modified, or distributed except
* according to those terms.
*/
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OrmShapeType {
pub iri: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OrmDiff {
}

@ -748,6 +748,11 @@ impl Verifier {
})),
)
.await;
let graph_nuri = NuriV0::repo_graph_name(
&update.repo_id,
&update.overlay_id,
);
self.orm_update(&NuriV0::new_empty(), update.transaction.as_quads_patch(graph_nuri)).await;
}
}
Ok(commit_nuris)

@ -18,6 +18,8 @@ mod user_storage;
mod commits;
pub mod orm;
mod request_processor;
mod inbox_processor;

@ -0,0 +1,144 @@
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers
// All rights reserved.
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.
use std::collections::HashMap;
use futures::channel::mpsc;
use futures::SinkExt;
pub use ng_net::orm::OrmShapeType;
pub use ng_net::orm::OrmDiff;
use ng_oxigraph::oxigraph::sparql::{results::*, Query, QueryResults};
use ng_oxigraph::oxrdf::Term;
use ng_oxigraph::oxrdf::Triple;
use ng_repo::log::*;
use ng_net::app_protocol::*;
use ng_net::{
connection::NoiseFSM,
types::*,
utils::{Receiver, Sender},
};
use ng_repo::errors::NgError;
use crate::types::*;
use crate::verifier::*;
impl Verifier {
fn sparql_construct(&self, query: String) -> Result<Vec<Triple>, NgError> {
let oxistore = self.graph_dataset.as_ref().unwrap();
// let graph_nuri = NuriV0::repo_graph_name(
// &update.repo_id,
// &update.overlay_id,
// );
//let base = NuriV0::repo_id(&repo.id);
let parsed = Query::parse(&query, None).map_err(|e| NgError::OxiGraphError(e.to_string()))?;
let results = oxistore
.query(parsed, None)
.map_err(|e| NgError::OxiGraphError(e.to_string()))?;
match results {
QueryResults::Graph(triples) => {
let mut results = vec![];
for t in triples {
match t {
Err(e) => { log_err!("{}",e.to_string()); return Err(NgError::SparqlError(e.to_string()))},
Ok(triple) => results.push(triple),
}
}
Ok(results)
}
_ => return Err(NgError::InvalidResponse),
}
}
fn sparql_select(&self, query: String) -> Result<Vec<Vec<Option<Term>>>, NgError> {
let oxistore = self.graph_dataset.as_ref().unwrap();
// let graph_nuri = NuriV0::repo_graph_name(
// &update.repo_id,
// &update.overlay_id,
// );
//let base = NuriV0::repo_id(&repo.id);
let parsed = Query::parse(&query, None).map_err(|e| NgError::OxiGraphError(e.to_string()))?;
let results = oxistore
.query(parsed, None)
.map_err(|e| NgError::OxiGraphError(e.to_string()))?;
match results {
QueryResults::Solutions(sols) => {
let mut results = vec![];
for t in sols {
match t {
Err(e) => { log_err!("{}",e.to_string()); return Err(NgError::SparqlError(e.to_string()))},
Ok(querysol) => results.push(querysol.values().to_vec()),
}
}
Ok(results)
}
_ => return Err(NgError::InvalidResponse),
}
}
pub(crate) async fn orm_update(&mut self, scope: &NuriV0, patch: GraphQuadsPatch) {
}
pub(crate) async fn frontend_update_orm(&mut self, scope: &NuriV0, shape_id: String, diff: OrmDiff) {
}
pub(crate) async fn push_orm_response(&mut self, scope: &NuriV0, schema_iri: &String, response: AppResponse) {
log_info!(
"push_orm_response {:?} {} {:?}",
scope,
schema_iri,
self.orm_subscriptions
);
if let Some(shapes) = self.orm_subscriptions.get_mut(scope) {
if let Some(sessions) = shapes.get_mut(schema_iri) {
let mut sessions_to_close : Vec<u64> = vec![];
for (session_id, sender) in sessions.iter_mut() {
if sender.is_closed() {
log_debug!("closed so removing session {}", session_id);
sessions_to_close.push(*session_id);
} else {
let _ = sender.send(response.clone()).await;
}
}
for session_id in sessions_to_close.iter() {
sessions.remove(session_id);
}
}
}
}
pub(crate) async fn start_orm(
&mut self,
nuri: &NuriV0,
schema: &OrmShapeType,
session_id: u64,
) -> Result<(Receiver<AppResponse>, CancelFn), NgError> {
let (tx, rx) = mpsc::unbounded::<AppResponse>();
self.orm_subscriptions.insert(nuri.clone(), HashMap::from([(schema.iri.clone(), HashMap::from([(session_id, tx.clone())]))]));
//self.push_orm_response().await;
let close = Box::new(move || {
//log_debug!("CLOSE_CHANNEL of subscription for branch {}", branch_id);
if !tx.is_closed() {
tx.close_channel();
}
});
Ok((rx, close))
}
}

@ -49,8 +49,17 @@ impl Verifier {
command: &AppRequestCommandV0,
nuri: &NuriV0,
_payload: &Option<AppRequestPayload>,
session_id: u64
) -> Result<(Receiver<AppResponse>, CancelFn), NgError> {
match command {
AppRequestCommandV0::OrmStart => {
match _payload {
Some(AppRequestPayload::V0(AppRequestPayloadV0::OrmStart(shape_type))) => {
self.start_orm(nuri, shape_type, session_id).await
},
_ => return Err(NgError::InvalidArgument)
}
},
AppRequestCommandV0::Fetch(fetch) => match fetch {
AppFetchContentV0::Subscribe => {
let (repo_id, branch_id, store_repo) =
@ -858,6 +867,14 @@ impl Verifier {
payload: Option<AppRequestPayload>,
) -> Result<AppResponse, NgError> {
match command {
AppRequestCommandV0::OrmUpdate => {
match payload {
Some(AppRequestPayload::V0(AppRequestPayloadV0::OrmUpdate((diff,shape_id)))) => {
self.frontend_update_orm(&nuri, shape_id, diff).await
},
_ => return Err(NgError::InvalidArgument)
}
},
AppRequestCommandV0::SocialQueryStart => {
let (from_profile, contacts_string, degree) = if let Some(AppRequestPayload::V0(AppRequestPayloadV0::SocialQueryStart{
from_profile, contacts, degree

@ -43,6 +43,11 @@ pub struct GraphTransaction {
pub removes: Vec<Triple>,
}
pub struct GraphQuadsPatch {
pub inserts: Vec<Quad>,
pub removes: Vec<Quad>,
}
const TOKENIZED_COMMIT: &str = "did:ng:_";
impl GraphTransaction {
@ -52,6 +57,12 @@ impl GraphTransaction {
removes: serde_bare::to_vec(&self.removes).unwrap(),
}
}
pub(crate) fn as_quads_patch(&self, graph_nuri: String) -> GraphQuadsPatch {
GraphQuadsPatch {
inserts: self.inserts.iter().map(|triple| triple.clone().in_graph(NamedNode::new(graph_nuri.clone()).unwrap())).collect(),
removes: self.removes.iter().map(|triple| triple.clone().in_graph(NamedNode::new(graph_nuri.clone()).unwrap())).collect(),
}
}
pub(crate) fn tokenize_with_commit_id(&mut self, commit_id: ObjectId, repo_id: &RepoId) {
for triple in self.inserts.iter_mut() {
if let Subject::NamedNode(nn) = &triple.subject {

@ -111,6 +111,7 @@ pub struct Verifier {
in_memory_outbox: Vec<EventOutboxStorage>,
uploads: BTreeMap<u32, RandomAccessFile>,
branch_subscriptions: HashMap<BranchId, Sender<AppResponse>>,
pub(crate) orm_subscriptions: HashMap<NuriV0, HashMap<String, HashMap<u64, Sender<AppResponse>>>>,
pub(crate) temporary_repo_certificates: HashMap<RepoId, ObjectRef>,
}
@ -516,6 +517,7 @@ impl Verifier {
inner_to_outer: HashMap::new(),
uploads: BTreeMap::new(),
branch_subscriptions: HashMap::new(),
orm_subscriptions: HashMap::new(),
temporary_repo_certificates: HashMap::new(),
}
}
@ -2779,6 +2781,7 @@ impl Verifier {
inner_to_outer: HashMap::new(),
uploads: BTreeMap::new(),
branch_subscriptions: HashMap::new(),
orm_subscriptions: HashMap::new(),
temporary_repo_certificates: HashMap::new(),
};
// this is important as it will load the last seq from storage
@ -2796,7 +2799,7 @@ impl Verifier {
) -> Result<(Receiver<AppResponse>, CancelFn), NgError> {
match req {
AppRequest::V0(v0) => {
self.process_stream(&v0.command, &v0.nuri, &v0.payload)
self.process_stream(&v0.command, &v0.nuri, &v0.payload, v0.session_id)
.await
}
}

@ -280,6 +280,8 @@ importers:
specifier: ^3.2.4
version: 3.2.4(@types/debug@4.1.12)(@types/node@24.3.0)(jiti@2.5.1)(jsdom@26.1.0)
sdk/ng-sdk-js/pkg: {}
packages:
'@ampproject/remapping@2.3.0':

@ -1,6 +1,7 @@
import * as shapeManager from "./shapeManager";
import type { WasmConnection, Diff, Scope } from "./types";
import type { ShapeType, BaseType } from "@nextgraph-monorepo/ng-shex-orm";
import * as ng from "@nextgraph-monorepo/ng-sdk-js";
import type { Person } from "../../shapes/ldo/personShape.typings";
import type { Cat } from "../../shapes/ldo/catShape.typings";
import type { TestObject } from "../../shapes/ldo/testShape.typings";
@ -94,9 +95,29 @@ communicationChannel.addEventListener(
"message",
(event: MessageEvent<WasmMessage>) => {
console.log("BACKEND: Received message", event.data);
// call WASM ng-sdk-js
const { type, connectionId, shapeType } = event.data;
if (type === "Request") {
/* unsub = await ng.orm_start(scope, shapeType, session_id,
async (response) => {
//console.log("GOT APP RESPONSE", response);
if (response.V0.OrmInitial) {
} else if (response.V0.OrmUpdate) {
let diff = response.V0.OrmUpdate.diff
const msg: WasmMessage = {
type: "BackendUpdate",
connectionId,
diff,
};
communicationChannel.postMessage(msg);
} else if (response.V0.OrmError) {
}
*/
const shapeId = shapeType?.shape;
const initialData = getInitialObjectByShapeId(shapeId);
@ -110,7 +131,7 @@ communicationChannel.addEventListener(
// Notify js-land about backend updates
const msg: WasmMessage = {
type: "BackendUpdate",
connectionId: conId,
connectionId,
diff,
};
communicationChannel.postMessage(msg);
@ -128,6 +149,7 @@ communicationChannel.addEventListener(
if (type === "Stop") {
shapeManager.connections.delete(connectionId);
// await ng.app_request ( OrmStop )
return;
}

@ -56,6 +56,7 @@ use ng_wallet::*;
use nextgraph::local_broker::*;
use nextgraph::verifier::CancelFn;
use nextgraph::verifier::orm::{OrmShapeType, OrmDiff};
use crate::model::*;
@ -1782,6 +1783,45 @@ pub async fn doc_subscribe(
app_request_stream_(request, callback).await
}
#[wasm_bindgen]
pub async fn orm_start(
scope: JsValue,
shapeType: JsValue,
session_id: JsValue,
callback: &js_sys::Function,
) -> Result<JsValue, String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let scope: NuriV0 = serde_wasm_bindgen::from_value::<NuriV0>(scope)
.map_err(|_| "Deserialization error of scope".to_string())?;
let shapeType: OrmShapeType = serde_wasm_bindgen::from_value::<OrmShapeType>(shapeType)
.map_err(|e| format!("Deserialization error of shapeType {e}"))?;
let mut request = AppRequest::new_orm_start(scope, shapeType);
request.set_session_id(session_id);
app_request_stream_(request, callback).await
}
#[wasm_bindgen]
pub async fn orm_update(
scope: JsValue,
shapeTypeName: String,
diff: JsValue,
session_id: JsValue,
) -> Result<(), String> {
let session_id: u64 = serde_wasm_bindgen::from_value::<u64>(session_id)
.map_err(|_| "Deserialization error of session_id".to_string())?;
let diff: OrmDiff = serde_wasm_bindgen::from_value::<OrmDiff>(diff)
.map_err(|e| format!("Deserialization error of diff {e}"))?;
let scope: NuriV0 = serde_wasm_bindgen::from_value::<NuriV0>(scope)
.map_err(|_| "Deserialization error of scope".to_string())?;
let mut request = AppRequest::new_orm_update(scope, shapeTypeName, diff);
request.set_session_id(session_id);
let response = nextgraph::local_broker::app_request(request)
.await
.map_err(|e: NgError| e.to_string())?;
Ok(())
}
// // #[wasm_bindgen]
// pub async fn get_readcap() -> Result<JsValue, String> {
// let request = ObjectRef::nil();

Loading…
Cancel
Save