Skip to content
This repository has been archived by the owner on Oct 25, 2024. It is now read-only.

Commit

Permalink
Add library with storage integration to replace graphql-lib (#1309)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlicanC authored and Rashad Alston committed Sep 19, 2023
1 parent f40a7ec commit 202b633
Show file tree
Hide file tree
Showing 66 changed files with 33,930 additions and 1,483 deletions.
479 changes: 302 additions & 177 deletions Cargo.lock

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ members = [
"packages/fuel-indexer-database/database-types",
"packages/fuel-indexer-database/postgres",
"packages/fuel-indexer-graphql",
"packages/fuel-indexer-graphql-lib",
"packages/fuel-indexer-graphql-dyn",
"packages/fuel-indexer-lib",
"packages/fuel-indexer-macros",
"packages/fuel-indexer-macros/macro-utils",
Expand All @@ -39,7 +39,7 @@ default-members = [
"packages/fuel-indexer-database/database-types",
"packages/fuel-indexer-database/postgres",
"packages/fuel-indexer-graphql",
"packages/fuel-indexer-graphql-lib",
"packages/fuel-indexer-graphql-dyn",
"packages/fuel-indexer-lib",
"packages/fuel-indexer-macros",
"packages/fuel-indexer-metrics",
Expand All @@ -66,8 +66,10 @@ rust-version = "1.72.0"
version = "0.20.8"

[workspace.dependencies]
async-graphql-parser = "5.0"
async-graphql-value = "5.0"
async-graphql = "6.0"
async-graphql-parser = "6.0"
async-graphql-value = "6.0"
async-graphql-axum = "6.0"
bincode = "1.3"
clap = "3.1"
forc-index = { version = "0.20.8", path = "./plugins/forc-index" }
Expand Down
4 changes: 2 additions & 2 deletions packages/fuel-indexer-api-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ path = "src/bin/main.rs"

[dependencies]
anyhow = "1.0"
async-graphql = "5.0.7"
async-graphql-axum = "5.0.7"
async-graphql = { workspace = true }
async-graphql-axum = { workspace = true }
async-std = "1"
axum = { version = "0.6", features = ["multipart", "macros"] }
clap = { features = ["cargo", "derive", "env"], workspace = true }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
[package]
name = "fuel-indexer-graphql-lib"
name = "fuel-indexer-graphql-dyn"
version = { workspace = true }
authors = { workspace = true }
edition = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
repository = { workspace = true }
rust-version = { workspace = true }
description = "Fuel Indexer GraphQL Library"
description = "Library for building executable dynamic GraphQL schemas"

[features]
default = ["json"]
default = ["json", "testing"]
json = ["dep:serde_json"]
testing = ["dep:graphql-parser"]

[dependencies]
async-graphql = { version = "5.0", features = ["dynamic-schema", "dataloader"] }
async-graphql-value = "6.0.3"
async-graphql = { workspace = true, features = ["dynamic-schema", "dataloader"] }
async-graphql-value = { workspace = true }
async-trait = "0.1.73"
extension-trait = "1.0.2"
graphql-parser = { version = "0.4.0", optional = true }
indexmap = "2.0.0"
serde_json = { workspace = true, optional = true }
strum = { version = "0.25.0", features = ["derive"] }
tokio = { workspace = true, features = ["sync"] }
thiserror = { workspace = true }
anyhow = "1.0"
lazy_static = "1.4.0"
convert_case = "0.6.0"
async-graphql-parser = "6.0.4"
serde.workspace = true

[dev-dependencies]
assert_matches = "1.5.0"
async-graphql-parser = "5.0"
async-graphql-parser = { workspace = true }
graphql-parser = "0.4.0"
insta = { version = "1.31.0", features = ["json"] }
insta = { version = "1.31.0", features = ["json", "ron"] }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["sync", "rt", "macros"] }
velcro = "0.5.4"
76 changes: 76 additions & 0 deletions packages/fuel-indexer-graphql-dyn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# fuel-indexer-graphql-dyn

GraphQL Dyn is a library for building executable dynamic GraphQL schemas.

Dyn has two main parts: the `store::Store` and the `schema::DynSchemaBuilder`. The schema builder is responsible for creating executable `async_graphql_dynamic::dynamic::Schema` instances from a dynamically defined `schema::DynSchemaType`. Built schema executes GraphQL queries by fetching data from the store. The store is responsible for communicating with the underlying data source.

## Usage

Users of Dyn will need three things to get started:

```rs
/// A `store::Store` implementation.
#[async_trait]
impl Store for MyStore {
fn r#type(&self) -> &StoreType { ... }
//
async fn obj_get(&self, id: &Id) -> R<O<Obj>> { ... }
async fn obj_get_many(&self, ids: &[Id]) -> R<Vec<O<Obj>>> { ... }
async fn assoc_get(&self, key: &Key, ...) -> R<Vec<O<Assoc>>> { ... }
async fn assoc_count(&self, key: &Key, ...) -> R<u64> { ... }
async fn assoc_range(&self, key: &Key, ...) -> R<Vec<Assoc>> { ... }
async fn assoc_time_range(&self, key: &Key, ...) -> R<Vec<Assoc>> { ... }
}

/// A `store::StoreType` constructor.
fn new_store_type(config: &MyConfig) -> StoreType {
let mut store_type = StoreTypeBuilder::default();
...
store_type.finish()
}

/// A `schema::DynSchemaType` constructor.
fn new_schema_type(config: &MyConfig, store_type: &StoreType) -> DynSchemaType {
let mut schema_type = DynSchemaTypeBuilder::new(store_type);
...
schema_type.finish()
}
```

With these implemented, you can create a dynamic schema and execute queries:

```rs
let store_type = new_store_type(&my_config)?;
let schema_type = new_schema_type(&my_config, &store_type)?;
let store = MyStore::new(&store_type);

let builder = DynSchemaBuilder::new(&schema_type, Arc::new(Mutex::new(store)));
let schema = builder.finish()?;

let response = schema.execute(Request::new(r#"
query {
chain(id: "Chain:0") {
name
...
blocks(first: 10) {
nodes {
number
...
transactions(first: 10) {
nodes {
index
...
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
}
}
}
}
"#)).await;
```
12 changes: 12 additions & 0 deletions packages/fuel-indexer-graphql-dyn/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#![allow(clippy::result_unit_err)]

pub mod schema;
pub mod spec;
pub mod store;
pub mod testing;

pub(self) mod self_prelude {
pub use anyhow::anyhow;
pub use async_trait::async_trait;
pub use extension_trait::extension_trait;
}
35 changes: 35 additions & 0 deletions packages/fuel-indexer-graphql-dyn/src/schema/connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use super::node::*;
use super::paging::*;
use super::self_prelude::*;

#[derive(Clone, PartialEq, Eq)]
pub struct DynConnectionEdge {
pub node_id: DynNodeId,
pub cursor: Cursor,
}

#[derive(Clone, PartialEq, Eq)]
pub struct DynConnection {
pub total_count: usize,
pub edges: Vec<DynConnectionEdge>,
pub page_info: DynPageInfo,
}

impl DynConnectionEdge {
pub fn new(
node_id: &(impl Into<DynNodeId> + Clone),
cursor: &(impl Into<Cursor> + Clone),
) -> Self {
Self {
node_id: node_id.clone().into(),
cursor: cursor.clone().into(),
}
}

pub fn node_id(&self) -> &DynNodeId {
&self.node_id
}
pub fn cursor(&self) -> &Cursor {
&self.cursor
}
}
151 changes: 151 additions & 0 deletions packages/fuel-indexer-graphql-dyn/src/schema/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use super::self_prelude::*;

#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum DynDataTypeId {
// GraphQL types
ID,
String,
Int,
Float,
Boolean,
List(Box<DynDataTypeId>),
// Option(Box<DynDataTypeId>),
// Fuel types
Unit,
U8,
U16,
U32,
U64,
B256,
Bytes,
Name(Name),
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum DynDataType {
// GraphQL types
ID,
String,
Int,
Float,
Boolean,
List(Box<DynDataType>),
// Option(Box<DynDataTypeId>),
// Fuel types
Unit,
U8,
U16,
U32,
U64,
B256,
Bytes,
Object(Name, IndexMap<DynDataFieldId, DynDataFieldType>),
Enum(Name, IndexMap<DynDataFieldId, DynDataFieldType>),
}

pub type DynDataFieldId = String;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DynDataFieldType {
pub name: Name,
pub data_type_id: DynDataTypeId,
pub store_data_field_id: store::DataFieldId,
}

impl From<store::DataTypeId> for DynDataTypeId {
fn from(store_data_type_id: store::DataTypeId) -> Self {
match store_data_type_id {
store::DataTypeId::Unit => Self::Unit,
store::DataTypeId::Bool => Self::Boolean,
store::DataTypeId::U8 => Self::U8,
store::DataTypeId::U16 => Self::U16,
store::DataTypeId::U32 => Self::U32,
store::DataTypeId::U64 => Self::U64,
store::DataTypeId::B256 => Self::B256,
store::DataTypeId::Byte => Self::U8,
store::DataTypeId::Bytes => Self::Bytes,
store::DataTypeId::String => Self::String,
store::DataTypeId::Array(ty) => Self::List(Box::new((*ty).into())),
store::DataTypeId::Name(name) => Self::Name(name),
}
}
}

impl FromStr for DynDataTypeId {
type Err = ();
fn from_str(str: &str) -> Result<Self, Self::Err> {
match str {
// GraphQL types
"ID" => Ok(Self::ID),
"String" => Ok(Self::String),
"Int" => Ok(Self::Int),
"Float" => Ok(Self::Float),
"Boolean" => Ok(Self::Boolean),
// List(Box<DynDataTypeId>),
// Option(Box<DynDataTypeId>),
// Fuel types
"Unit" => Ok(Self::Unit),
"U8" => Ok(Self::U8),
"U16" => Ok(Self::U16),
"U32" => Ok(Self::U32),
"U64" => Ok(Self::U64),
"B256" => Ok(Self::B256),
"Bytes" => Ok(Self::Bytes),
str => Ok(Self::Name(Name::new_pascal(str))),
}
}
}

impl std::fmt::Display for DynDataTypeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ID => write!(f, "ID"),
Self::Int => write!(f, "Int"),
Self::Float => write!(f, "Float"),
Self::List(ty) => write!(f, "[{}]", ty),
// Self::Option(ty) => write!(f, "{}", ty),
Self::Unit => write!(f, "Unit"),
Self::Boolean => write!(f, "Boolean"),
Self::U8 => write!(f, "U8"),
Self::U16 => write!(f, "U16"),
Self::U32 => write!(f, "U32"),
Self::U64 => write!(f, "U64"),
Self::B256 => write!(f, "B256"),
Self::Bytes => write!(f, "Bytes"),
Self::String => write!(f, "String"),
Self::Name(str) => write!(f, "{}", str),
}
}
}

impl DynDataType {
pub fn name(&self) -> Name {
match self {
Self::ID => Name::new_pascal("ID"),
Self::Int => Name::new_pascal("Int"),
Self::Float => Name::new_pascal("Float"),
Self::List(ty) => Name::new_pascal(format!("[{}]", ty.name())),
// Self::Option(ty) => Name::new_pascal(&format!("{}!", ty)),
Self::Unit => Name::new_pascal("Unit"),
Self::Boolean => Name::new_pascal("Boolean"),
Self::U8 => Name::new_pascal("U8"),
Self::U16 => Name::new_pascal("U16"),
Self::U32 => Name::new_pascal("U32"),
Self::U64 => Name::new_pascal("U64"),
Self::B256 => Name::new_pascal("B256"),
Self::Bytes => Name::new_pascal("Bytes"),
Self::String => Name::new_pascal("String"),
Self::Object(name, _) => Name::new_pascal(format!("{}", name)),
Self::Enum(name, _) => Name::new_pascal(format!("{}", name)),
}
}
}

impl DynDataFieldType {
pub fn data_type_id(&self) -> &DynDataTypeId {
&self.data_type_id
}
pub fn store_id(&self) -> &store::DataFieldId {
&self.store_data_field_id
}
}
Loading

0 comments on commit 202b633

Please sign in to comment.