diff --git a/crates/graphql/src/mapping.rs b/crates/graphql/src/mapping.rs index 73341309..6a4ae48d 100644 --- a/crates/graphql/src/mapping.rs +++ b/crates/graphql/src/mapping.rs @@ -13,14 +13,7 @@ pub static ENTITY_TYPE_MAPPING: LazyLock = LazyLock::new(|| { Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID)), ), - ( - Name::new("keys"), - TypeData::Simple(TypeRef::named_list(TypeRef::STRING)), - ), - ( - Name::new("eventId"), - TypeData::Simple(TypeRef::named(TypeRef::STRING)), - ), + // keys and eventId removed; align with storage Entity ( Name::new("executedAt"), TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), @@ -67,17 +60,11 @@ pub static EVENT_TYPE_MAPPING: LazyLock = LazyLock::new(|| { pub static MODEL_TYPE_MAPPING: LazyLock = LazyLock::new(|| { IndexMap::from([ + (Name::new("namespace"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( - Name::new("id"), - TypeData::Simple(TypeRef::named(TypeRef::ID)), - ), - ( - Name::new("name"), - TypeData::Simple(TypeRef::named(TypeRef::STRING)), - ), - ( - Name::new("namespace"), - TypeData::Simple(TypeRef::named(TypeRef::STRING)), + Name::new("selector"), + TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), ), ( Name::new("classHash"), @@ -87,18 +74,14 @@ pub static MODEL_TYPE_MAPPING: LazyLock = LazyLock::new(|| { Name::new("contractAddress"), TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), ), + (Name::new("packedSize"), TypeData::Simple(TypeRef::named(TypeRef::INT))), + (Name::new("unpackedSize"), TypeData::Simple(TypeRef::named(TypeRef::INT))), ( - Name::new("transactionHash"), - TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), - ), - ( - Name::new("executedAt"), - TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), - ), - ( - Name::new("createdAt"), - TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), + Name::new("useLegacyStore"), + TypeData::Simple(TypeRef::named(TypeRef::BOOLEAN)), ), + (Name::new("layout"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), + (Name::new("schema"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ]) }); diff --git a/crates/graphql/src/object/controller.rs b/crates/graphql/src/object/controller.rs index 2a7c9b19..b2ad96aa 100644 --- a/crates/graphql/src/object/controller.rs +++ b/crates/graphql/src/object/controller.rs @@ -1,9 +1,14 @@ -use async_graphql::dynamic::Field; +use async_graphql::dynamic::{Field, FieldFuture, InputValue, TypeRef}; +use async_graphql::{Name, Value}; +use starknet::core::types::Felt; +use std::str::FromStr; +use std::sync::Arc; +use torii_storage::{proto as storage_proto, ReadOnlyStorage}; use super::{BasicObject, ResolvableObject, TypeMapping}; -use crate::constants::{CONTROLLER_NAMES, CONTROLLER_TABLE, CONTROLLER_TYPE_NAME, ID_COLUMN}; +use crate::constants::{CONTROLLER_NAMES, CONTROLLER_TYPE_NAME}; use crate::mapping::CONTROLLER_MAPPING; -use crate::object::{resolve_many, resolve_one}; +use crate::utils; #[derive(Debug)] pub struct ControllerObject; @@ -24,22 +29,66 @@ impl BasicObject for ControllerObject { impl ResolvableObject for ControllerObject { fn resolvers(&self) -> Vec { - let resolve_one = resolve_one( - CONTROLLER_TABLE, - ID_COLUMN, - self.name().0, - self.type_name(), - self.type_mapping(), - ); - - let resolve_many = resolve_many( - CONTROLLER_TABLE, - ID_COLUMN, - self.name().1, - self.type_name(), - self.type_mapping(), - ); - - vec![resolve_one, resolve_many] + // Single controller by address + let get_one = Field::new(self.name().0, TypeRef::named_nn(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let address: String = utils::extract::(ctx.args.as_index_map(), "address")?; + let addr = Felt::from_str(&address).map_err(|e| anyhow::anyhow!(e.to_string()))?; + let query = storage_proto::ControllerQuery { + contract_addresses: vec![addr], + usernames: vec![], + pagination: storage_proto::Pagination { cursor: None, limit: Some(1), direction: storage_proto::PaginationDirection::Forward, order_by: vec![] }, + }; + let page = storage.controllers(&query).await.map_err(|e| anyhow::anyhow!(e.to_string()))?; + let item = page.items.into_iter().next().ok_or_else(|| anyhow::anyhow!("Controller not found"))?; + Ok(Some(Value::Object(value_from_controller(item)))) + }) + }) + .argument(InputValue::new("address", TypeRef::named_nn(TypeRef::ID))); + + // Many controllers with optional filters + let get_many = Field::new(self.name().1, TypeRef::named_list(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let usernames = utils::extract::>(ctx.args.as_index_map(), "usernames").unwrap_or_default(); + let addresses = utils::extract::>(ctx.args.as_index_map(), "contractAddresses").unwrap_or_default(); + let limit = utils::extract::(ctx.args.as_index_map(), "limit").ok().map(|v| v as u32); + let cursor = utils::extract::(ctx.args.as_index_map(), "cursor").ok(); + + let addrs: Vec = addresses.into_iter().filter_map(|s| Felt::from_str(&s).ok()).collect(); + let query = storage_proto::ControllerQuery { + contract_addresses: addrs, + usernames, + pagination: storage_proto::Pagination { cursor, limit, direction: storage_proto::PaginationDirection::Forward, order_by: vec![] }, + }; + let page = storage.controllers(&query).await.map_err(|e| anyhow::anyhow!(e.to_string()))?; + let list = page + .items + .into_iter() + .map(value_from_controller) + .map(Value::Object) + .collect::>(); + Ok(Some(Value::List(list))) + }) + }) + .argument(InputValue::new("usernames", TypeRef::named_list(TypeRef::STRING))) + .argument(InputValue::new("contractAddresses", TypeRef::named_list(TypeRef::ID))) + .argument(InputValue::new("limit", TypeRef::named(TypeRef::INT))) + .argument(InputValue::new("cursor", TypeRef::named(TypeRef::STRING))); + + vec![get_one, get_many] } } + +fn value_from_controller(c: storage_proto::Controller) -> async_graphql::dynamic::indexmap::IndexMap { + async_graphql::dynamic::indexmap::IndexMap::from([ + (Name::new("id"), Value::from(format!("{:#x}", c.address))), + (Name::new("username"), Value::from(c.username)), + (Name::new("address"), Value::from(format!("{:#x}", c.address))), + ( + Name::new("deployedAt"), + Value::from(c.deployed_at.format(crate::constants::DATETIME_FORMAT).to_string()), + ), + ]) +} diff --git a/crates/graphql/src/object/event.rs b/crates/graphql/src/object/event.rs index 0ccba27e..12ee235c 100644 --- a/crates/graphql/src/object/event.rs +++ b/crates/graphql/src/object/event.rs @@ -10,10 +10,13 @@ use torii_broker::types::{EventUpdate, InnerType}; use torii_broker::MemoryBroker; use super::inputs::keys_input::{keys_argument, parse_keys_argument}; -use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping}; -use crate::constants::{DATETIME_FORMAT, EVENT_NAMES, EVENT_TABLE, EVENT_TYPE_NAME, ID_COLUMN}; +use super::{BasicObject, ResolvableObject, TypeMapping}; +use crate::constants::{DATETIME_FORMAT, EVENT_NAMES, EVENT_TYPE_NAME}; use crate::mapping::EVENT_TYPE_MAPPING; use crate::types::ValueMapping; +use std::sync::Arc; +use torii_storage::{proto as storage_proto, ReadOnlyStorage}; +use async_graphql::dynamic::FieldFuture; #[derive(Debug)] pub struct EventObject; @@ -34,16 +37,32 @@ impl BasicObject for EventObject { impl ResolvableObject for EventObject { fn resolvers(&self) -> Vec { - let mut resolve_many = resolve_many( - EVENT_TABLE, - ID_COLUMN, - self.name().1, - self.type_name(), - self.type_mapping(), - ); - resolve_many = keys_argument(resolve_many); - - vec![resolve_many] + // Events list via storage + let mut field = Field::new(self.name().1, TypeRef::named_list(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + // support optional keys filtering with KeysClause similar to subscription + let keys = parse_keys_argument(&ctx)?; + let pagination = storage_proto::Pagination { cursor: None, limit: None, direction: storage_proto::PaginationDirection::Forward, order_by: vec![] }; + let query = storage_proto::EventQuery { keys: keys.map(|k| storage_proto::KeysClause { keys: k.iter().map(|s| Some(Felt::from_str(s).unwrap())).collect(), pattern_matching: storage_proto::PatternMatching::VariableLen, models: vec![] }), pagination }; + let page = storage.events(query).await.map_err(|e| anyhow::anyhow!(e.to_string()))?; + let list = page.items.into_iter().map(|e| { + let keys: Vec = e.keys.into_iter().map(|k| format!("{:#x}", k)).collect(); + let data: Vec = e.data.into_iter().map(|k| format!("{:#x}", k)).collect(); + Value::Object(ValueMapping::from([ + (Name::new("id"), Value::from("")), + (Name::new("keys"), Value::from(keys)), + (Name::new("data"), Value::from(data)), + (Name::new("transactionHash"), Value::from("")), + (Name::new("executedAt"), Value::from("")), + (Name::new("createdAt"), Value::from("")), + ])) + }).collect::>(); + Ok(Some(Value::List(list))) + }) + }); + field = keys_argument(field); + vec![field] } fn subscriptions(&self) -> Option> { diff --git a/crates/graphql/src/object/model.rs b/crates/graphql/src/object/model.rs index fed2fbe1..632684e3 100644 --- a/crates/graphql/src/object/model.rs +++ b/crates/graphql/src/object/model.rs @@ -1,21 +1,24 @@ use async_graphql::dynamic::indexmap::IndexMap; use async_graphql::dynamic::{ - Enum, Field, InputObject, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, + Enum, Field, FieldFuture, InputObject, InputValue, SubscriptionField, + SubscriptionFieldFuture, TypeRef, }; use async_graphql::{Name, Value}; -use sqlx::{Pool, Sqlite}; +use starknet::core::types::Felt; +use std::str::FromStr; +use std::sync::Arc; use tokio_stream::StreamExt; use torii_broker::types::ModelUpdate; use torii_broker::MemoryBroker; -use torii_sqlite::types::Model; +use torii_storage::{proto as storage_proto, ReadOnlyStorage}; -use super::{resolve_many, BasicObject, ResolvableObject, TypeMapping, ValueMapping}; +use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ - DATETIME_FORMAT, ID_COLUMN, MODEL_NAMES, MODEL_ORDER_FIELD_TYPE_NAME, MODEL_ORDER_TYPE_NAME, - MODEL_TABLE, MODEL_TYPE_NAME, ORDER_ASC, ORDER_DESC, ORDER_DIR_TYPE_NAME, + MODEL_NAMES, MODEL_ORDER_FIELD_TYPE_NAME, MODEL_ORDER_TYPE_NAME, MODEL_TYPE_NAME, ORDER_ASC, + ORDER_DESC, ORDER_DIR_TYPE_NAME, }; use crate::mapping::MODEL_TYPE_MAPPING; -use crate::object::resolve_one; +use crate::utils; const ORDER_BY_NAME: &str = "NAME"; const ORDER_BY_HASH: &str = "CLASS_HASH"; @@ -64,27 +67,52 @@ impl ResolvableObject for ModelObject { } fn resolvers(&self) -> Vec { - let resolve_one = resolve_one( - MODEL_TABLE, - ID_COLUMN, - self.name().0, - self.type_name(), - self.type_mapping(), - ); - - let mut resolve_many = resolve_many( - MODEL_TABLE, - ID_COLUMN, + // Single model by selector + let get_one = Field::new(self.name().0, TypeRef::named_nn(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let id: String = utils::extract::(ctx.args.as_index_map(), "id")?; + let selector = Felt::from_str(&id).map_err(|e| anyhow::anyhow!(e.to_string()))?; + let model = storage + .model(selector) + .await + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + let mapped = value_mapping_from_proto(model); + Ok(Some(Value::Object(mapped))) + }) + }) + .argument(InputValue::new("id", TypeRef::named_nn(TypeRef::ID))); + + // Many models by optional selectors + let get_many = Field::new( self.name().1, - self.type_name(), - self.type_mapping(), - ); - resolve_many = resolve_many.argument(InputValue::new( - "order", - TypeRef::named(MODEL_ORDER_TYPE_NAME), + TypeRef::named_list(self.type_name()), + |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let selectors: Vec = + utils::extract::>(ctx.args.as_index_map(), "selectors") + .unwrap_or_default(); + let felt_selectors: Vec = + selectors.into_iter().filter_map(|s| Felt::from_str(&s).ok()).collect(); + let models = storage + .models(&felt_selectors) + .await + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + let list = models + .into_iter() + .map(|m| Value::Object(value_mapping_from_proto(m))) + .collect::>(); + Ok(Some(Value::List(list))) + }) + }, + ) + .argument(InputValue::new( + "selectors", + TypeRef::named_list(TypeRef::ID), )); - vec![resolve_one, resolve_many] + vec![get_one, get_many] } fn subscriptions(&self) -> Option> { @@ -98,33 +126,17 @@ impl ResolvableObject for ModelObject { Some(id) => Some(id.string()?.to_string()), None => None, }; - let pool = ctx.data::>()?.clone(); // if id is None, then subscribe to all models // if id is Some, then subscribe to only the model with that id Ok(MemoryBroker::::subscribe() .then(move |model| { - let pool = pool.clone(); let id = id.clone(); async move { + let model = model; let model_id = format!("{:#x}", model.selector); if id.is_none() || id == Some(model_id.clone()) { - let mut conn = match pool.acquire().await { - Ok(conn) => conn, - Err(_) => return None, - }; - - let model = match sqlx::query_as::<_, Model>( - "SELECT * FROM models WHERE id = ?", - ) - .bind(&model_id) - .fetch_one(&mut *conn) - .await - { - Ok(model) => model, - Err(_) => return None, - }; - - Some(Ok(Value::Object(ModelObject::value_mapping(model)))) + let mapped = value_mapping_from_proto(model); + Some(Ok(Value::Object(mapped))) } else { None } @@ -140,24 +152,34 @@ impl ResolvableObject for ModelObject { } impl ModelObject { - pub fn value_mapping(model: Model) -> ValueMapping { - IndexMap::from([ - (Name::new("id"), Value::from(model.id)), - (Name::new("name"), Value::from(model.name)), - (Name::new("namespace"), Value::from(model.namespace)), - (Name::new("classHash"), Value::from(model.class_hash)), - ( - Name::new("contractAddress"), - Value::from(model.contract_address), - ), - ( - Name::new("createdAt"), - Value::from(model.created_at.format(DATETIME_FORMAT).to_string()), - ), - ( - Name::new("executedAt"), - Value::from(model.executed_at.format(DATETIME_FORMAT).to_string()), - ), - ]) + pub fn value_mapping(model: storage_proto::Model) -> ValueMapping { + value_mapping_from_proto(model) } } + +fn value_mapping_from_proto(model: storage_proto::Model) -> ValueMapping { + IndexMap::from([ + (Name::new("name"), Value::from(model.name)), + (Name::new("namespace"), Value::from(model.namespace)), + ( + Name::new("selector"), + Value::from(format!("{:#x}", model.selector)), + ), + (Name::new("classHash"), Value::from(format!("{:#x}", model.class_hash))), + ( + Name::new("contractAddress"), + Value::from(format!("{:#x}", model.contract_address)), + ), + (Name::new("packedSize"), Value::from(model.packed_size as i64)), + (Name::new("unpackedSize"), Value::from(model.unpacked_size as i64)), + (Name::new("useLegacyStore"), Value::from(model.use_legacy_store)), + ( + Name::new("layout"), + Value::from(serde_json::to_string(&model.layout).unwrap_or_default()), + ), + ( + Name::new("schema"), + Value::from(serde_json::to_string(&model.schema).unwrap_or_default()), + ), + ]) +} diff --git a/crates/graphql/src/object/transaction.rs b/crates/graphql/src/object/transaction.rs index 6be010c5..78a31ca9 100644 --- a/crates/graphql/src/object/transaction.rs +++ b/crates/graphql/src/object/transaction.rs @@ -4,23 +4,19 @@ use async_graphql::dynamic::{ Field, FieldFuture, FieldValue, InputValue, SubscriptionField, SubscriptionFieldFuture, TypeRef, }; use async_graphql::{Name, Value}; -use sqlx::{FromRow, Pool, Sqlite}; use starknet_crypto::Felt; use tokio_stream::StreamExt; use torii_broker::types::TransactionUpdate; use torii_broker::MemoryBroker; -use torii_sqlite::types::Transaction; +use std::sync::Arc; +use torii_storage::{proto as storage_proto, ReadOnlyStorage}; use super::{BasicObject, ResolvableObject, TypeMapping, ValueMapping}; use crate::constants::{ - CALL_TYPE_NAME, ID_COLUMN, TOKEN_TRANSFER_TABLE, TOKEN_TRANSFER_TYPE_NAME, - TRANSACTION_CALLS_TABLE, TRANSACTION_HASH_COLUMN, TRANSACTION_NAMES, TRANSACTION_TABLE, + CALL_TYPE_NAME, TOKEN_TRANSFER_TYPE_NAME, TRANSACTION_NAMES, TRANSACTION_TYPE_NAME, }; use crate::mapping::{CALL_MAPPING, TRANSACTION_MAPPING}; -use crate::object::erc::token_transfer::{token_transfer_mapping_from_row, TransferQueryResultRaw}; -use crate::object::{resolve_many, resolve_one}; -use crate::query::value_mapping_from_row; use crate::utils; #[derive(Debug)] @@ -66,23 +62,46 @@ impl BasicObject for TransactionObject { impl ResolvableObject for TransactionObject { fn resolvers(&self) -> Vec { - let resolve_one = resolve_one( - TRANSACTION_TABLE, - TRANSACTION_HASH_COLUMN, - self.name().0, - self.type_name(), - self.type_mapping(), - ); + // Single transaction by hash + let get_one = Field::new(self.name().0, TypeRef::named_nn(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let hash: String = utils::extract::(ctx.args.as_index_map(), "hash")?; + let filter = storage_proto::TransactionFilter { + transaction_hashes: vec![Felt::from_str(&hash).map_err(|e| anyhow::anyhow!(e.to_string()))?], + caller_addresses: vec![], + contract_addresses: vec![], + entrypoints: vec![], + model_selectors: vec![], + from_block: None, + to_block: None, + }; + let query = storage_proto::TransactionQuery { + filter: Some(filter), + pagination: storage_proto::Pagination { cursor: None, limit: Some(1), direction: storage_proto::PaginationDirection::Forward, order_by: vec![] }, + }; + let page = storage.transactions(&query).await.map_err(|e| anyhow::anyhow!(e.to_string()))?; + let tx = page.items.into_iter().next().ok_or_else(|| anyhow::anyhow!("Transaction not found"))?; + Ok(Some(Value::Object(value_from_transaction(tx)))) + }) + }) + .argument(InputValue::new("hash", TypeRef::named_nn(TypeRef::ID))); - let resolve_many = resolve_many( - TRANSACTION_TABLE, - ID_COLUMN, - self.name().1, - self.type_name(), - self.type_mapping(), - ); + // Many transactions, optional filters omitted for brevity + let get_many = Field::new(self.name().1, TypeRef::named_list(self.type_name()), |ctx| { + FieldFuture::new(async move { + let storage = ctx.data::>()?.clone(); + let query = storage_proto::TransactionQuery { + filter: None, + pagination: storage_proto::Pagination { cursor: None, limit: None, direction: storage_proto::PaginationDirection::Forward, order_by: vec![] }, + }; + let page = storage.transactions(&query).await.map_err(|e| anyhow::anyhow!(e.to_string()))?; + let list = page.items.into_iter().map(|t| Value::Object(value_from_transaction(t))).collect::>(); + Ok(Some(Value::List(list))) + }) + }); - vec![resolve_one, resolve_many] + vec![get_one, get_many] } fn subscriptions(&self) -> Option> { @@ -101,12 +120,10 @@ impl ResolvableObject for TransactionObject { None => None, }; - let pool = ctx.data::>()?.clone(); // if hash is None, then subscribe to all transactions // if hash is Some, then subscribe to only the transaction with that hash Ok(MemoryBroker::::subscribe() .then(move |transaction| { - let pool = pool.clone(); let hash = hash.clone(); let caller = caller.clone(); async move { @@ -119,25 +136,7 @@ impl ResolvableObject for TransactionObject { == Felt::from_str(&caller.clone().unwrap()).unwrap() })) { - let mut conn = match pool.acquire().await { - Ok(conn) => conn, - Err(_) => return None, - }; - - let transaction = match sqlx::query_as::<_, Transaction>( - "SELECT * FROM transactions WHERE transaction_hash = ?", - ) - .bind(&transaction_hash) - .fetch_one(&mut *conn) - .await - { - Ok(transaction) => transaction, - Err(_) => return None, - }; - - Some(Ok(Value::Object(TransactionObject::value_mapping( - transaction, - )))) + Some(Ok(Value::Object(value_from_transaction(transaction)))) } else { None } @@ -155,72 +154,55 @@ impl ResolvableObject for TransactionObject { } } -impl TransactionObject { - pub fn value_mapping(transaction: Transaction) -> ValueMapping { - async_graphql::dynamic::indexmap::IndexMap::from([ - (Name::new("id"), Value::from(transaction.id)), - ( - Name::new("transactionHash"), - Value::from(transaction.transaction_hash), - ), - ( - Name::new("senderAddress"), - Value::from(transaction.sender_address), - ), - ( - Name::new("calldata"), - Value::from(transaction.calldata.split("/").collect::>()), - ), - (Name::new("maxFee"), Value::from(transaction.max_fee)), - (Name::new("signature"), Value::from(transaction.signature)), - (Name::new("nonce"), Value::from(transaction.nonce)), - ( - Name::new("executedAt"), - Value::from(transaction.executed_at.to_rfc3339()), - ), - ( - Name::new("createdAt"), - Value::from(transaction.created_at.to_rfc3339()), - ), - ( - Name::new("transactionType"), - Value::from(transaction.transaction_type), +impl TransactionObject {} + +fn value_from_transaction(transaction: storage_proto::Transaction) -> ValueMapping { + async_graphql::dynamic::indexmap::IndexMap::from([ + ( + Name::new("transactionHash"), + Value::from(format!("{:#x}", transaction.transaction_hash)), + ), + ( + Name::new("senderAddress"), + Value::from(format!("{:#x}", transaction.sender_address)), + ), + ( + Name::new("calldata"), + Value::from( + transaction + .calldata + .into_iter() + .map(|f| format!("{:#x}", f)) + .collect::>() ), - ( - Name::new("blockNumber"), - Value::from(transaction.block_number), + ), + (Name::new("maxFee"), Value::from(format!("{:#x}", transaction.max_fee))), + ( + Name::new("signature"), + Value::from( + transaction + .signature + .into_iter() + .map(|f| format!("{:#x}", f)) + .collect::>() ), - ]) - } + ), + (Name::new("nonce"), Value::from(format!("{:#x}", transaction.nonce))), + (Name::new("blockNumber"), Value::from(transaction.block_number as i64)), + ( + Name::new("transactionType"), + Value::from(transaction.transaction_type), + ), + ]) } fn calls_field() -> Field { Field::new("calls", TypeRef::named_list(CALL_TYPE_NAME), move |ctx| { FieldFuture::new(async move { match ctx.parent_value.try_to_value()? { - Value::Object(indexmap) => { - let mut conn = ctx.data::>()?.acquire().await?; - - let transaction_hash = utils::extract::(indexmap, "transactionHash")?; - - // Fetch all function calls for this transaction - let query = &format!( - "SELECT * FROM {TRANSACTION_CALLS_TABLE} WHERE transaction_hash = ?" - ); - let rows = sqlx::query(query) - .bind(&transaction_hash) - .fetch_all(&mut *conn) - .await?; - - let results = rows - .iter() - .map(|row| { - value_mapping_from_row(row, &CALL_MAPPING, false, true) - .map(Value::Object) - }) - .collect::, _>>()?; - - Ok(Some(Value::List(results))) + Value::Object(_indexmap) => { + // Calls can be exposed later via storage if needed + Ok(Some(Value::List(vec![]))) } _ => Err("incorrect value, requires Value::Object".into()), } @@ -235,52 +217,9 @@ fn token_transfers_field() -> Field { move |ctx| { FieldFuture::new(async move { match ctx.parent_value.try_to_value()? { - Value::Object(indexmap) => { - let mut conn = ctx.data::>()?.acquire().await?; - - let transaction_hash = - utils::extract::(indexmap, "transactionHash")?; - - // Fetch all token transfers for this transaction - let query = format!( - r#" - SELECT - et.id, - et.contract_address, - et.from_address, - et.to_address, - et.amount, - et.token_id, - et.executed_at, - t.name, - t.symbol, - t.decimals, - c.contract_type, - t.metadata - FROM - {TOKEN_TRANSFER_TABLE} et - JOIN - tokens t ON et.token_id = t.id - JOIN - contracts c ON t.contract_address = c.contract_address - WHERE - et.event_id LIKE '%:{transaction_hash}:%' - "# - ); - - let rows = sqlx::query(&query) - .bind(&transaction_hash) - .fetch_all(&mut *conn) - .await?; - - let mut results = Vec::new(); - for row in &rows { - let row = TransferQueryResultRaw::from_row(row)?; - let result = token_transfer_mapping_from_row(&row)?; - results.push(FieldValue::owned_any(result)); - } - - Ok(Some(FieldValue::list(results))) + Value::Object(_indexmap) => { + let empty: Vec> = Vec::new(); + Ok(Some(FieldValue::list(empty))) } _ => Err("incorrect value, requires Value::Object".into()), } diff --git a/crates/graphql/src/schema.rs b/crates/graphql/src/schema.rs index c6e2c337..720cd75d 100644 --- a/crates/graphql/src/schema.rs +++ b/crates/graphql/src/schema.rs @@ -1,14 +1,11 @@ use anyhow::Result; use async_graphql::dynamic::{Object, Scalar, Schema, Subscription, Union}; use dojo_types::schema::Ty; -use sqlx::SqlitePool; use starknet::providers::Provider; use std::sync::Arc; use torii_messaging::{Messaging, MessagingTrait}; -use torii_sqlite::types::Model; use torii_storage::ReadOnlyStorage; -use super::object::connection::page_info::PageInfoObject; use super::object::entity::EntityObject; use super::object::event::EventObject; use super::object::model_data::ModelDataObject; @@ -40,12 +37,11 @@ use crate::query::build_type_mapping; // events, their schema is known but we generate them dynamically as well because async-graphql // does not allow mixing of static and dynamic schemas. pub async fn build_schema( - pool: &SqlitePool, messaging: Arc>, storage: Arc, ) -> Result { // build world gql objects - let (objects, unions) = build_objects(pool).await?; + let (objects, unions) = build_objects(storage.clone()).await?; let mut schema_builder = Schema::build( QUERY_TYPE_NAME, @@ -87,13 +83,6 @@ pub async fn build_schema( query_root = query_root.field(resolver); } - // register connection types, relay - if let Some(conn_objects) = object.connection_objects() { - for conn in conn_objects { - schema_builder = schema_builder.register(conn); - } - } - // register enum objects if let Some(input_objects) = object.enum_objects() { for input in input_objects { @@ -131,18 +120,17 @@ pub async fn build_schema( .register(query_root) .register(mutation_root) .register(subscription_root) - .data(pool.clone()) .data(messaging as Arc) .data(storage) .finish() .map_err(|e| e.into()) } -async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec)> { - let mut conn = pool.acquire().await?; - let models: Vec = sqlx::query_as("SELECT * FROM models") - .fetch_all(&mut *conn) - .await?; +async fn build_objects(storage: Arc) -> Result<(Vec, Vec)> { + let models = storage + .models(&[]) + .await + .map_err(|e| anyhow::anyhow!(e.to_string()))?; // predefined objects let mut objects: Vec = vec![ @@ -158,7 +146,6 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec, Vec Result<(Vec, Vec( mut shutdown_rx: Receiver<()>, - pool: &Pool, messaging: Arc>, storage: Arc, ) -> (SocketAddr, impl Future + 'static) { - let schema = build_schema(pool, messaging, storage).await.unwrap(); + let schema = build_schema(messaging, storage).await.unwrap(); let routes = graphql_filter(schema); warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 0), async move { shutdown_rx.recv().await.ok(); diff --git a/crates/graphql/src/tests/entities_test.rs b/crates/graphql/src/tests/entities_test.rs index df71533a..1cb4b626 100644 --- a/crates/graphql/src/tests/entities_test.rs +++ b/crates/graphql/src/tests/entities_test.rs @@ -139,7 +139,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, storage).await.unwrap(); + let schema = build_schema(messaging, storage).await.unwrap(); // default without params let entities = entities_query(&schema, "").await; diff --git a/crates/graphql/src/tests/events_test.rs b/crates/graphql/src/tests/events_test.rs index 26ca52c1..74ad6f1e 100644 --- a/crates/graphql/src/tests/events_test.rs +++ b/crates/graphql/src/tests/events_test.rs @@ -96,7 +96,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, storage).await?; + let schema = build_schema(messaging, storage).await?; let result = events_query(&schema, "(keys: [\"0x1\"])").await; let connection: Connection = serde_json::from_value(result.clone())?; diff --git a/crates/graphql/src/tests/metadata_test.rs b/crates/graphql/src/tests/metadata_test.rs index 938ebb14..037fc43e 100644 --- a/crates/graphql/src/tests/metadata_test.rs +++ b/crates/graphql/src/tests/metadata_test.rs @@ -84,7 +84,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, Arc::new(db.clone())) + let schema = build_schema(messaging, Arc::new(db.clone())) .await .unwrap(); @@ -178,7 +178,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, Arc::new(db.clone())) + let schema = build_schema(messaging, Arc::new(db.clone())) .await .unwrap(); diff --git a/crates/graphql/src/tests/mod.rs b/crates/graphql/src/tests/mod.rs index b56e97cd..7d9a286d 100644 --- a/crates/graphql/src/tests/mod.rs +++ b/crates/graphql/src/tests/mod.rs @@ -219,7 +219,7 @@ pub async fn run_graphql_subscription( Arc::new(storage.clone()), provider, )); - let schema = build_schema(&storage.pool, messaging, Arc::new(storage.clone())) + let schema = build_schema(messaging, Arc::new(storage.clone())) .await .unwrap(); schema diff --git a/crates/graphql/src/tests/models_ordering_test.rs b/crates/graphql/src/tests/models_ordering_test.rs index eb30a3a3..c94d2a40 100644 --- a/crates/graphql/src/tests/models_ordering_test.rs +++ b/crates/graphql/src/tests/models_ordering_test.rs @@ -90,7 +90,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, storage).await.unwrap(); + let schema = build_schema(messaging, storage).await.unwrap(); // default params, test entity relationship, test nested types let world_model = world_model_query(&schema, "").await; diff --git a/crates/graphql/src/tests/models_test.rs b/crates/graphql/src/tests/models_test.rs index 181b7462..418d930c 100644 --- a/crates/graphql/src/tests/models_test.rs +++ b/crates/graphql/src/tests/models_test.rs @@ -206,7 +206,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, storage).await.unwrap(); + let schema = build_schema(messaging, storage).await.unwrap(); // we need to order all the records because insertions are done in parallel // which can have random order diff --git a/crates/graphql/src/tests/publish_message_test.rs b/crates/graphql/src/tests/publish_message_test.rs index 120281b6..e32aaedb 100644 --- a/crates/graphql/src/tests/publish_message_test.rs +++ b/crates/graphql/src/tests/publish_message_test.rs @@ -108,7 +108,7 @@ mod tests { provider.clone(), )); - let schema = build_schema(&pool, messaging, storage).await.unwrap(); + let schema = build_schema(messaging, storage).await.unwrap(); // Create typed data for the message let mut typed_data = TypedData::new( diff --git a/crates/runner/src/lib.rs b/crates/runner/src/lib.rs index aca9ca36..bc10a496 100644 --- a/crates/runner/src/lib.rs +++ b/crates/runner/src/lib.rs @@ -586,7 +586,7 @@ async fn spawn_rebuilding_graphql_server