Skip to content

Commit

Permalink
torii-client fetch remote if entities not synced (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy authored Oct 30, 2023
1 parent c861726 commit 0cb3113
Show file tree
Hide file tree
Showing 11 changed files with 119 additions and 70 deletions.
Binary file modified bun.lockb
Binary file not shown.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ set -ex

# This example requires to *not* create ES modules, therefore we pass the flag
# `--target no-modules`
wasm-pack build --out-dir ../wasm --release --features console-error-panic
wasm-pack build --out-dir ../pkg --release --features console-error-panic
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
use std::str::FromStr;

use dojo_types::schema::EntityModel;
use futures::StreamExt;
use starknet::core::types::FieldElement;
use starknet::core::utils::cairo_short_string_to_felt;
use types::ClientConfig;
use types::{ClientConfig, IEntityModel};
use wasm_bindgen::prelude::*;

mod types;
Expand All @@ -14,7 +15,6 @@ mod utils;
use utils::parse_ty_as_json_str;

type JsFieldElement = JsValue;
type JsEntityModel = JsValue;

#[wasm_bindgen]
extern "C" {
Expand All @@ -32,9 +32,9 @@ pub struct Client {

#[wasm_bindgen]
impl Client {
/// Retrieves the model value of an entity.
/// Retrieves the model value of an entity. Will fetch from remote if the requested entity is not one of the entities that are being synced.
#[wasm_bindgen(js_name = getModelValue)]
pub fn get_model_value(
pub async fn get_model_value(
&self,
model: &str,
keys: Vec<JsFieldElement>,
Expand All @@ -46,40 +46,47 @@ impl Client {
.into_iter()
.map(serde_wasm_bindgen::from_value::<FieldElement>)
.collect::<Result<Vec<FieldElement>, _>>()
.map_err(|err| {
JsValue::from_str(format!("failed to parse entity keys: {err}").as_str())
})?;
.map_err(|err| JsValue::from(format!("failed to parse entity keys: {err}")))?;

match self
.inner
.entity(&EntityModel {
keys,
model: model.to_string(),
})
.await
{
Ok(Some(ty)) => Ok(js_sys::JSON::parse(&parse_ty_as_json_str(&ty).to_string())?),
Ok(None) => Ok(JsValue::NULL),

match self.inner.entity(model, &keys) {
Some(ty) => Ok(serde_wasm_bindgen::to_value(&parse_ty_as_json_str(&ty))?),
None => Ok(JsValue::NULL),
Err(err) => Err(JsValue::from(format!("failed to get entity: {err}"))),
}
}

/// Register new entities to be synced.
#[wasm_bindgen(js_name = addEntitiesToSync)]
pub async fn add_entities_to_sync(&self, entities: Vec<JsEntityModel>) -> Result<(), JsValue> {
pub async fn add_entities_to_sync(&self, entities: Vec<IEntityModel>) -> Result<(), JsValue> {
log("adding entities to sync...");

#[cfg(feature = "console-error-panic")]
console_error_panic_hook::set_once();

let entities = entities
.into_iter()
.map(serde_wasm_bindgen::from_value::<dojo_types::schema::EntityModel>)
.map(|e| serde_wasm_bindgen::from_value(e.into()))
.collect::<Result<Vec<_>, _>>()?;

self.inner
.add_entities_to_sync(entities)
.await
.map_err(|err| JsValue::from_str(&err.to_string()))
.map_err(|err| JsValue::from(err.to_string()))
}

/// Remove the entities from being synced.
#[wasm_bindgen(js_name = removeEntitiesToSync)]
pub async fn remove_entities_to_sync(
&self,
entities: Vec<JsEntityModel>,
entities: Vec<IEntityModel>,
) -> Result<(), JsValue> {
log("removing entities to sync...");

Expand All @@ -88,28 +95,33 @@ impl Client {

let entities = entities
.into_iter()
.map(serde_wasm_bindgen::from_value::<dojo_types::schema::EntityModel>)
.map(|e| serde_wasm_bindgen::from_value(e.into()))
.collect::<Result<Vec<_>, _>>()?;

self.inner
.remove_entities_to_sync(entities)
.await
.map_err(|err| JsValue::from_str(&err.to_string()))
.map_err(|err| JsValue::from(err.to_string()))
}

/// Register a callback to be called every time the specified entity change.
#[wasm_bindgen(js_name = onEntityChange)]
pub fn on_entity_change(
/// Register a callback to be called every time the specified synced entity's value changes.
#[wasm_bindgen(js_name = onSyncEntityChange)]
pub fn on_sync_entity_change(
&self,
entity: JsEntityModel,
entity: IEntityModel,
callback: js_sys::Function,
) -> Result<(), JsValue> {
#[cfg(feature = "console-error-panic")]
console_error_panic_hook::set_once();

let entity = serde_wasm_bindgen::from_value::<dojo_types::schema::EntityModel>(entity)?;
let entity =
serde_wasm_bindgen::from_value::<dojo_types::schema::EntityModel>(entity.into())?;
let model = cairo_short_string_to_felt(&entity.model).expect("invalid model name");
let mut rcv = self.inner.storage().add_listener(model, &entity.keys).unwrap();
let mut rcv = self
.inner
.storage()
.add_listener(model, &entity.keys)
.unwrap();

wasm_bindgen_futures::spawn_local(async move {
while rcv.next().await.is_some() {
Expand All @@ -123,34 +135,38 @@ impl Client {

/// Create the a client with the given configurations.
#[wasm_bindgen(js_name = createClient)]
#[allow(non_snake_case)]
pub async fn create_client(
initial_entities_to_sync: Vec<JsEntityModel>,
initialEntitiesToSync: Vec<IEntityModel>,
config: ClientConfig,
) -> Result<Client, JsValue> {
#[cfg(feature = "console-error-panic")]
console_error_panic_hook::set_once();

let ClientConfig { rpc_url, torii_url, world_address } = config;
let ClientConfig {
rpc_url,
torii_url,
world_address,
} = config;

let entities = initial_entities_to_sync
let entities = initialEntitiesToSync
.into_iter()
.map(serde_wasm_bindgen::from_value::<dojo_types::schema::EntityModel>)
.map(|e| serde_wasm_bindgen::from_value(e.into()))
.collect::<Result<Vec<_>, _>>()?;

let world_address = FieldElement::from_str(&world_address).map_err(|err| {
JsValue::from_str(format!("failed to parse world address: {err}").as_str())
})?;
let world_address = FieldElement::from_str(&world_address)
.map_err(|err| JsValue::from(format!("failed to parse world address: {err}")))?;

let client = torii_client::client::ClientBuilder::new()
.set_entities_to_sync(entities)
.build(torii_url, rpc_url, world_address)
.await
.map_err(|err| JsValue::from_str(format!("failed to build client: {err}").as_str()))?;
.map_err(|err| JsValue::from(format!("failed to build client: {err}")))?;

wasm_bindgen_futures::spawn_local(client.start_subscription().await.map_err(|err| {
JsValue::from_str(
format!("failed to start torii client subscription service: {err}").as_str(),
)
JsValue::from(format!(
"failed to start torii client subscription service: {err}"
))
})?);

Ok(Client { inner: client })
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
use serde::{Deserialize, Serialize};
use tsify::Tsify;
use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen(typescript_custom_section)]
pub const ENTITY_MODEL_STR: &'static str = r#"
export interface EntityModel {
model: string;
keys: string[];
}
"#;

#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "EntityModel")]
pub type IEntityModel;
}

#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,28 @@ use dojo_types::primitive::Primitive;
use dojo_types::schema::Ty;
use serde_json::Value;

pub fn parse_ty_as_json_str(ty: &Ty) -> String {
fn parse_ty_as_json_str_impl(ty: &Ty) -> Value {
match ty {
Ty::Primitive(primitive) => primitive_value_json(*primitive),
pub fn parse_ty_as_json_str(ty: &Ty) -> Value {
match ty {
Ty::Primitive(primitive) => primitive_value_json(*primitive),

Ty::Struct(struct_ty) => struct_ty
.children
.iter()
.map(|child| (child.name.to_owned(), parse_ty_as_json_str_impl(&child.ty)))
.collect::<serde_json::Map<String, Value>>()
.into(),
Ty::Struct(struct_ty) => struct_ty
.children
.iter()
.map(|child| (child.name.to_owned(), parse_ty_as_json_str(&child.ty)))
.collect::<serde_json::Map<String, Value>>()
.into(),

Ty::Enum(enum_ty) => {
if let Some(option) = enum_ty.option {
let option = &enum_ty.options[option as usize];
Value::String(option.name.to_owned())
} else {
Value::Null
}
Ty::Enum(enum_ty) => {
if let Some(option) = enum_ty.option {
let option = &enum_ty.options[option as usize];
Value::String(option.name.to_owned())
} else {
Value::Null
}

Ty::Tuple(_) => unimplemented!("tuple not supported"),
}
}

parse_ty_as_json_str_impl(ty).to_string()
Ty::Tuple(_) => unimplemented!("tuple not supported"),
}
}

fn primitive_value_json(primitive: Primitive) -> Value {
Expand Down Expand Up @@ -84,8 +80,14 @@ mod test {
name: "PlayerKind".into(),
option: Some(1),
options: vec![
EnumOption { name: "Good".into(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Bad".into(), ty: Ty::Tuple(vec![]) },
EnumOption {
name: "Good".into(),
ty: Ty::Tuple(vec![]),
},
EnumOption {
name: "Bad".into(),
ty: Ty::Tuple(vec![]),
},
],
}),
},
Expand Down Expand Up @@ -148,8 +150,14 @@ mod test {
name: "PlayerKind".into(),
option: Some(1),
options: vec![
EnumOption { name: "Good".into(), ty: Ty::Tuple(vec![]) },
EnumOption { name: "Bad".into(), ty: Ty::Tuple(vec![]) },
EnumOption {
name: "Good".into(),
ty: Ty::Tuple(vec![]),
},
EnumOption {
name: "Bad".into(),
ty: Ty::Tuple(vec![]),
},
],
}),
},
Expand Down
16 changes: 13 additions & 3 deletions packages/torii-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "dist/index.js",
"type": "module",
"scripts": {
"build-wasm": "cd rust && ./build.sh",
"build-wasm": "cd crate && ./build.sh",
"build": "npm run build-wasm && tsc"
},
"exports": {
Expand All @@ -18,7 +18,17 @@
"author": "",
"license": "MIT",
"dependencies": {
"typescript": "^5.0.3",
"torii-client-wasm": "link:dojo-packages/packages/torii-client/wasm"
"starknet": "^5.19.5",
"torii-client-wasm": "link:dojo-packages/packages/torii-client/pkg",
"typescript": "^5.0.3"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/mocha": "^10.0.1",
"bun-types": "^1.0.1",
"fetch-mock": "^9.11.0",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"tsup": "^7.2.0"
}
}

0 comments on commit 0cb3113

Please sign in to comment.