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

Add library with storage integration to replace graphql-lib #1309

Merged
merged 1 commit into from
Sep 1, 2023

Conversation

AlicanC
Copy link
Contributor

@AlicanC AlicanC commented Aug 29, 2023

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:

/// 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:

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;

Copy link
Contributor

@ra0x3 ra0x3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AlicanC

  • Wow, obviously a ton of good work here thanks so much for this 🥲 🥇
  • Left some comments (obviously no way I can possibly digest the entire 33K line change 😆 )
    • And again, obviously none of this feedback has to be addressed before merging into the feature branch
    • But some of it will have to be addressed before merging into develop

Float,
Boolean,
List(Box<DynDataTypeId>),
// Option(Box<DynDataTypeId>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

Float,
Boolean,
List(Box<DynDataType>),
// Option(Box<DynDataTypeId>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

"Int" => Ok(Self::Int),
"Float" => Ok(Self::Float),
"Boolean" => Ok(Self::Boolean),
// List(Box<DynDataTypeId>),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

Self::Int => write!(f, "Int"),
Self::Float => write!(f, "Float"),
Self::List(ty) => write!(f, "[{}]", ty),
// Self::Option(ty) => write!(f, "{}", ty),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

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)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dead code?

.or_insert(DynDataTypeBuilder::Object(DynObjectTypeBuilder::new(name)))
{
DynDataTypeBuilder::Object(builder) => builder,
_ => unreachable!(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add a message to unreachable! that explains why this is unreachable? (In case the path gets hit) - or at least a comment


impl Data {
pub fn r#type(&self) -> &DataTypeId {
todo!()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this pending anything in particular?

.iter_mut()
.find(|(_, data)| data.name() == name)
.map(|(id, data)| (id.clone(), data))
.unwrap_or_else(|| panic!("Data not found: {}", name))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway we can fail gracefully here with Result rather than panic ? (similar thoughts for the rest of the panic in this module)

@@ -0,0 +1,127 @@
---
source: packages/fuel-indexer-graphql-dyn/src/testing/schema.rs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lovely 👌🏽

use insta::*;

#[tokio::test]
async fn test() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

@AlicanC
Copy link
Contributor Author

AlicanC commented Sep 1, 2023

@ra0x3 thanks for having a look!

Most (if not all) unpolished parts are pending a discussion as you've guessed and it is about store::Data.

In store::DataType, for simplicity, I used our ABI types with the addition of a few necessities that are just Sway proposals ATM.

We should discuss and decide what types we should have. We already have an enum ColumnType but all store::{Store, Data, Obj, Assoc} are going to be very very hot and to achieve a stable and performant implementation we should restrict ourselves to a small amount of primitives just like we do for ABI types. (We can directly use those too. Maybe we should if we want to use FVM instead of WASM in the future?) Anything higher level can be defined at the schema level.

When we finalize store::DataType and then store::Data then a lot of code will be removed from the schema module and it can finally be cleaned up from all the todo()!s and such.

@ra0x3 ra0x3 merged commit d749813 into feature/dynamic-graphql Sep 1, 2023
@ra0x3 ra0x3 deleted the jc/graphql-dyn branch September 1, 2023 18:26
@ra0x3
Copy link
Contributor

ra0x3 commented Sep 1, 2023

@AlicanC Gonna merge this so I can put up my PR on Monday - feel free to revert if you want :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants