Git Product home page Git Product logo

graph-client's Introduction

The Graph Client Tools

This repo is the home for The Graph consumer-side tools (for both browser and NodeJS environments).

Background

The tools provided in this repo are intended to enrich and extend the DX, and add the additional layer required for dApps in order to implement distributed applications.

Developers who consume data from The Graph GraphQL API often need peripherals for making data consumption easier, and also tools that allow using multiple indexers at the same time.

Features and Goals

This library is intended to simplify the network aspect of data consumption for dApps. The tools provided within this repository are intended to run at build time, in order to make execution faster and performant at runtime.

The tools provided in this repo can be used as standalone, but you can also use it with any existing GraphQL Client!

Status Feature Notes
Multiple indexers based on fetch strategies
Fetch Strategies timeout, retry, fallback, race, highestValue
Build time validations & optimizations
Client-Side Composition with improved execution planner (based on GraphQL-Mesh)
Cross-chain Subgraph Handling Use similar subgraphs as a single source
Raw Execution (standalone mode) without a wrapping GraphQL client
Local (client-side) Mutations
Automatic Block Tracking tracking block numbers as described here
Automatic Pagination doing multiple requests in a single call to fetch more than the indexer limit
Integration with @apollo/client
Integration with urql
TypeScript support with built-in GraphQL Codegen and TypedDocumentNode
@live queries Based on polling

You can find an extended architecture design here

Getting Started

You can follow Episode 45 of graphql.wtf to learn more about Graph Client:

GraphQL.wtf Episode 45

To get started, make sure to install [The Graph Client CLI] in your project:

yarn add -D @graphprotocol/client-cli
# or, with NPM:
npm install --save-dev @graphprotocol/client-cli

The CLI is installed as dev dependency since we are using it to produce optimized runtime artifacts that can be loaded directly from your app!

Create a configuration file (called .graphclientrc.yml) and point to your GraphQL endpoints provided by The Graph, for example:

# .graphclientrc.yml
sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2

Now, create a runtime artifact by running The Graph Client CLI:

graphclient build

Note: you need to run this with yarn prefix, or add that as a script in your package.json.

This should produce a ready-to-use standalone execute function, that you can use for running your application GraphQL operations, you should have an output similar to the following:

GraphClient: Cleaning existing artifacts
GraphClient: Reading the configuration
🕸️: Generating the unified schema
🕸️: Generating artifacts
🕸️: Generating index file in TypeScript
🕸️: Writing index.ts for ESM to the disk.
🕸️: Cleanup
🕸️: Done! => .graphclient

Now, the .graphclient artifact is generated for you, and you can import it directly from your code, and run your queries:

import { execute } from '../.graphclient'

const myQuery = gql`
  query pairs {
    pair(id: "0x00004ee988665cdda9a1080d5792cecd16dc1220") {
      id
      token0 {
        id
        symbol
        name
      }
      token1 {
        id
        symbol
        name
      }
    }
  }
`

async function main() {
  const result = await execute(myQuery, {})
  console.log(result)
}

main()

Using Vanilla JavaScript Instead of TypeScript

GraphClient CLI generates the client artifacts as TypeScript files by default, but you can configure CLI to generate JavaScript and JSON files together with additional TypeScript definition files by using --fileType js or --fileType json.

js flag generates all files as JavaScript files with ESM Syntax and json flag generates source artifacts as JSON files while entrypoint JavaScript file with old CommonJS syntax because only CommonJS supports JSON files as modules.

Unless you use CommonJS(require) specifically, we'd recommend you to use js flag.

graphclient --fileType js

The Graph Client DevTools

The Graph Client CLI comes with a built-in GraphiQL, so you can experiment with queries in real-time.

The GraphQL schema served in that environment, is the eventual schema based on all composed Subgraphs and transformations you applied.

To start the DevTool GraphiQL, run the following command:

graphclient serve-dev

And open http://localhost:4000/ to use GraphiQL. You can now experiment with your Graph client-side GraphQL schema locally! 🥳

Examples

You can also refer to examples directory in this repo, for more advanced examples and integration examples:

Advanced Examples/Features

Customize Network Calls

You can customize the network execution (for example, to add authentication headers) by using operationHeaders:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        operationHeaders:
          Authorization: Bearer MY_TOKEN

You can also use runtime variables if you wish, and specify it in a declarative way:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        operationHeaders:
          Authorization: Bearer {context.config.apiToken}

Then, you can specify that when you execute operations:

execute(myQuery, myVariables, {
  config: {
    apiToken: 'MY_TOKEN',
  },
})

You can find the complete documentation for the graphql handler here.

Environment Variables Interpolation

If you wish to use environment variables in your Graph Client configuration file, you can use interpolation with env helper:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        operationHeaders:
          Authorization: Bearer {env.MY_API_TOKEN} # runtime

Then, make sure to have MY_API_TOKEN defined when you run process.env at runtime.

You can also specify environment variables to be filled at build time (during graphclient build run) by using the env-var name directly:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        operationHeaders:
          Authorization: Bearer ${MY_API_TOKEN} # build time

You can find the complete documentation for the graphql handler here.

Fetch Strategies and Multiple Graph Indexers

It's a common practice to use more than one indexer in dApps, so to achieve the ideal experience with The Graph, you can specify several fetch strategies in order to make it more smooth and simple.

All fetch strategies can be combined to create the ultimate execution flow.

`retry`

The retry mechanism allow you to specify the retry attempts for a single GraphQL endpoint/source.

The retry flow will execute in both conditions: a netword error, or due to a runtime error (indexing issue/inavailability of the indexer).

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        retry: 2 # specify here, if you have an unstable/error prone indexer
`timeout`

The timeout mechanism allow you to specify the timeout for a given GraphQL endpoint.

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
        timeout: 5000 # 5 seconds
`fallback`

The fallback mechanism allow you to specify use more than one GraphQL endpoint, for the same source.

This is useful if you want to use more than one indexer for the same Subgraph, and fallback when an error/timeout happens. You can also use this strategy in order to use a custom indexer, but allow it to fallback to The Graph Hosted Service.

sources:
  - name: uniswapv2
    handler:
      graphql:
        strategy: fallback
        sources:
          - endpoint: https://bad-uniswap-v2-api.com
            retry: 2
            timeout: 5000
          - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
`race`

The race mechanism allow you to specify use more than one GraphQL endpoint, for the same source, and race on every execution.

This is useful if you want to use more than one indexer for the same Subgraph, and allow both sources to race and get the fastest response from all specified indexers.

sources:
  - name: uniswapv2
    handler:
      graphql:
        strategy: race
        sources:
          - endpoint: https://bad-uniswap-v2-api.com
          - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
`highestValue`

This strategy allows you to send parallel requests to different endpoints for the same source and choose the most updated.

This is useful if you want to choose most synced data for the same Subgraph over different indexers/sources.

sources:
  - name: uniswapv2
    handler:
      graphql:
        strategy: highestValue
        strategyConfig:
          selectionSet: |
            {
              _meta {
                block {
                  number
                }
              }
            }
          value: '_meta.block.number'
        sources:
          - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2-1
          - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2-2
graph LR;
    subgraph most-synced
    req(Outgoing Query)-->sA[Subgraph A];
    sA-->d{MostSyncedStrategy};
    d-->s1[Source 1];
    d-->s2[Source 2];
    s1-->synced["process"]
    s2-->synced
    synced-->|"max(_meta.block_number)"|d
    end
Loading

Block Tracking

The Graph Client can track block numbers and do the following queries by following this pattern with blockTracking transform;

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
    transforms:
      - blockTracking:
          # You might want to disable schema validation for faster startup
          validateSchema: true
          # Ignore the fields that you don't want to be tracked
          ignoreFieldNames: [users, prices]
          # Exclude the operation with the following names
          ignoreOperationNames: [NotFollowed]

You can try a working example here

Automatic Pagination

With most subgraphs, the number of records you can fetch is limited. In this case, you have to send multiple requests with pagination.

query {
  # Will throw an error if the limit is 1000
  users(first: 2000) {
    id
    name
  }
}

So you have to send the following operations one after the other:

query {
  # Will throw an error if the limit is 1000
  users(first: 1000) {
    id
    name
  }
}

Then after the first response:

query {
  # Will throw an error if the limit is 1000
  users(first: 1000, skip: 1000) {
    id
    name
  }
}

After the second response, you have to merge the results manually. But instead The Graph Client allows you to do the first one and automatically does those multiple requests for you under the hood.

All you have to do is:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
    transforms:
      - autoPagination:
          # You might want to disable schema validation for faster startup
          validateSchema: true

You can try a working example here

Client-side Composition

The Graph Client has built-in support for client-side GraphQL Composition (powered by GraphQL-Tools Schema-Stitching).

You can leverage this feature in order to create a single GraphQL layer from multiple Subgraphs, deployed on multiple indexers.

💡 Tip: You can compose any GraphQL sources, and not only Subgraphs!

Trivial composition can be done by adding more than one GraphQL source to your .graphclientrc.yml file, here's an example:

sources:
  - name: uniswapv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2
  - name: compoundv2
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/graphprotocol/compound-v2

As long as there a no conflicts across the composed schemas, you can compose it, and then run a single query to both Subgraphs:

query myQuery {
  # this one is coming from compound-v2
  markets(first: 7) {
    borrowRate
  }
  # this one is coming from uniswap-v2
  pair(id: "0x00004ee988665cdda9a1080d5792cecd16dc1220") {
    id
    token0 {
      id
    }
    token1 {
      id
    }
  }
}

You can also resolve conflicts, rename parts of the schema, add custom GraphQL fields, and modify the entire execution phase.

For advanced use-cases with composition, please refer to the following resources:

TypeScript Support

If your project is written in TypeScript, you can leverage the power of TypedDocumentNode and have a fully-typed GraphQL client experience.

The standalone mode of The GraphQL, and popular GraphQL client libraries like Apollo-Client and urql has built-in support for TypedDocumentNode!

The Graph Client CLI comes with a ready-to-use configuration for GraphQL Code Generator, and it can generate TypedDocumentNode based on your GraphQL operations.

To get started, define your GraphQL operations in your application code, and point to those files using the documents section of .graphclientrc.yml:

sources:
  -  # ... your Subgraphs/GQL sources here

documents:
  - ./src/example-query.graphql

You can also use Glob expressions, or even point to code files, and the CLI will find your GraphQL queries automatically:

documents:
  - './src/**/*.graphql'
  - './src/**/*.{ts,tsx,js,jsx}'

Now, run the GraphQL CLI build command again, the CLI will generate a TypedDocumentNode object under .graphclient for every operation found.

Make sure to name your GraphQL operations, otherwise it will be ignored!

For example, a query called query ExampleQuery will have the corresponding ExampleQueryDocument generated in .graphclient. You can now import it and use that for your GraphQL calls, and you'll have a fully typed experience without writing or specifying any TypeScript manually:

import { ExampleQueryDocument, execute } from '../.graphclient'

async function main() {
  // "result" variable is fully typed, and represents the exact structure of the fields you selected in your query.
  const result = await execute(ExampleQueryDocument, {})
  console.log(result)
}

You can find a TypeScript project example here.

Client-Side Mutations

Due to the nature of Graph-Client setup, it is possible to add client-side schema, that you can later bridge to run any arbitrary code.

This is helpful since you can implement custom code as part of your GraphQL schema, and have it as unified application schema that is easier to track and develop.

This document explains how to add custom mutations, but in fact you can add any GraphQL operation (query/mutation/subscriptions). See Extending the unified schema article for more information about this feature.

To get started, define a additionalTypeDefs section in your config file:

additionalTypeDefs: |
  # We should define the missing `Mutation` type
  extend schema {
    mutation: Mutation
  }

  type Mutation {
    doSomething(input: SomeCustomInput!): Boolean!
  }

  input SomeCustomInput {
    field: String!
  }

Then, add a pointer to a custom GraphQL resolvers file:

additionalResolvers:
  - './resolvers'

Now, create resolver.js (or, resolvers.ts) in your project, and implement your custom mutation:

module.exports = {
  Mutation: {
    async doSomething(root, args, context, info) {
      // Here, you can run anything you wish.
      // For example, use `web3` lib, connect a wallet and so on.

      return true
    },
  },
}

If you are using TypeScript, you can also get fully type-safe signature by doing:

import { Resolvers } from './.graphclient'

// Now it's fully typed!
const resolvers: Resolvers = {
  Mutation: {
    async doSomething(root, args, context, info) {
      // Here, you can run anything you wish.
      // For example, use `web3` lib, connect a wallet and so on.

      return true
    },
  },
}

export default resolvers

If you need to inject runtime variables into your GraphQL execution context, you can use the following snippet:

execute(
  MY_QUERY,
  {},
  {
    myHelper: {}, // this will be available in your Mutation resolver as `context.myHelper`
  },
)

You can read more about client-side schema extensions here

You can also delegate and call Query fields as part of your mutation

License

Released under the MIT license.

graph-client's People

Contributors

ardatan avatar azf20 avatar dotansimha avatar github-actions[bot] avatar n1ru4l avatar omahs avatar renovate[bot] avatar saihaj avatar theguild-bot avatar vuittont60 avatar witherblock avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graph-client's Issues

TypeError: Class extends value undefined is not a constructor or null

I'm having this issue running graph-build after upgrading the client-cli to 2.2.4
I'm using this build command for cjs: graphclient build --fileType json

Screenshot 2022-09-04 at 4 05 22 PM

When I upgraded the cli, it showed the warnings below
Screenshot 2022-09-04 at 4 03 03 PM

I added the dependencies, but it didn't solve the TypeError when I run the build command.
Screenshot 2022-09-04 at 4 16 25 PM

Research possible support for indexer/admin GraphQL endpoints

This architecture is really awesome. if we could have a health-check endpoint to get chainHeadBlock, its also helpful.
I think the following is an ideal configuration.

Current configuration ==>

sources:

Proposed configuration ==>

sources:

Originally posted by @wanglonghong in #46 (comment)

Unexpected behavior from first & where

const { liquidityPositions }: { liquidityPositions: LiquidityPosition[] } = await sdk.LiquidityPositions({
first: 1000,
where: { user: MAKER_ADDRESS[chainId] },
})

You would think this would be "WHERE user = MAKER_ADDRESS" return first 1000 enitites where this condition is met.

However, The GraphQL implementation by The Graph does not work in this way, it is scanning the first 1000 rows, and returning entities where that condition is met. This is very confusing behavior.... And means that for the above usage, first would have to be the total number of LiquidityPosition's ever known in the system, regardless of user.

Built-in pagination for making it easier to deal with `graph-node` limitations?

The goal of this idea is to have an option to allow users to fetch more than the limit of graph-node, without being concerned about orchestrating calls.

Today, the limit is 1000 records per page.

For example, if a user needs to have more than 1000 (for example 5000), they should be able to specify it in their GraphQL request, and then graph-client can orchestrate the 5 calls to get the list of data, and then merge it into a single response.

Graphclient generates unknown type `DocumentNode`

Error details

File: .graphclient/index.ts
error: Parameter 'doc' of exported function has or is using private name 'DocumentNode'.ts(4078)

export async function getBuiltGraphSDK<TGlobalContext = any, TOperationContext = any>(globalContext?: TGlobalContext) {
  const { sdkRequesterFactory } = await getBuiltGraphClient();
  return getSdk<TOperationContext>(sdkRequesterFactory(globalContext));
}

export type Requester<C = {}> = <R, V>(doc: **_DocumentNode_**, vars?: V, options?: C) => Promise<R>;
export function getSdk<C>(requester: Requester<C>) {
  return {};
}
export type Sdk = ReturnType<typeof getSdk>;

How to reproduce the above issue?

Its so simple. just to run the following script. graphclient build --tsOnly. This command generates typescript files but also generates unknown type => DocumentNode. This type throws when trying to build the typescript project using tsc --build ./tsconfig.build.json

Temporary Fix

If I replace DocumentNode with any after graphclient build, typescript build works. but it is not a preferrable way.
Is there a way to fix this when running graphclient build --tsOnly

Thanks

Decentralized network: support for fallback

The goal of this issue is to address a pain point when working with multiple, decentralized indexers. Some GraphQL schemas might be consumed through multiple GraphQL endpoints, from different indexers.
Some indexers might not be available, so we aim to allow the option to fallback on a schema source (maybe also fallback to the hosted service)

Alternative for GraphQL Subscriptions over WebSocket

Today, graph-node Subscriptions transport is not 100% reliable and uses a legacy protocol.

We are looking into different ways of providing an alternative to that.

Possible solutions:

  • subscribe-as-query: write a GraphQL Subscription, but choose a custom hook (Smart contract event? just polling?)
  • query + hook

Order by nested relation

a (orderBy: b.value) {
  b {
    value
  }
}

I assume this is limitation with GraphQL, and/or implementation of it by The Graph, since I'm unable to do a simple orderBy: b.value

Have any suggestions here? I wonder if we can work around this with graph client since not being able to order by a property nested 1 level deep seems silly...

Sorry to tag but I'm sure you work some magic @ardatan

Support Subgraphs deployed in cross-chain mode

Based on our talk and discussions with multiple users, we saw a need to support identical Subgraphs that are deployed on different chains.

This issue tries to cover the needs, use-cases, and requirements to support this feature as part of the client-side composition feature.

Use-case 1: Subgraphs are managed separately and prefixed

Status: ✅ (can be implemented now without any changes to graph-client)

flowchart TB
    linkStyle default interpolate basis
    c1[(Chain 1)]-->t1
    c2[(Chain 2)]-->t2

    subgraph "Subgraph A"
    t1("type Something")
    end

    subgraph "Subgraph B"
    t2("type Something")
    end

    t1---ccc
    t2---ccc
    ccc["Client-side composition"]---gc["Graph Client"]

    subgraph m["Composed Schema"]
    t3("type A_Something")
    t4("type B_Something")
    t5("type Query {\na_something: [A_Something!]!\nb_something: [B_Something!]! \n}")

    end

    gc---m
Loading

In such a scenario, developers might want to use multiple Subgraphs that have the same definition but might run on different chains. The fact that both are running under the same Subgraph definition will lead to a conflict due to the fact that both schemas have the same type X (even if the schemas/typedefs are the same).

In this case, a developer might want to prefix each schema types while being compose, at the level of each source:

sources:
  - name: subgraphA
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/test/a
    transforms:
      - prefix:
          value: A_
          includeRootOperations: true
          ignore:
            - _SubgraphErrorPolicy_
  - name: subgraphB
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/test/b
    transforms:
      - prefix:
          value: B_
          includeRootOperations: true
          ignore:
            - _SubgraphErrorPolicy_

This way, the merged schema can allow users to execute one query, but orchestrate the call through multiple chains:

query something($t: String) { # you can even pass the same GraphQL variable, into multiple GraphQL field arguments
   A_something(t: $t) {
     id
   }
   B_something(t: $t) {
     id
   }
}

Use-case 2: Merge responses

Status: ❓

flowchart TB
    linkStyle default interpolate basis
    c1[(Chain 1)]-->t1
    c2[(Chain 2)]-->t2

    subgraph "Subgraph A"
    t1("type Something")
    end

    subgraph "Subgraph B"
    t2("type Something")
    end

    t1---ccc
    t2---ccc
    ccc["Client-side composition"]---gc["Graph Client"]

    subgraph m["Composed Schema"]
    t3("type Something")
    t4("type Query {\n something: [Something!]! \n}")
    end

    gc---m
Loading

In such a scenario, the user doesn't care which Subgraph originates which data, and the data is just being merged together, this could be useful for fetching lists of objects cross-chains.

query something($t: String) {
   something(t: $t) { # this will return a list of Something, originated on both A and B Subgraphs
     id
   }
}

Side-note: this might break pagination since you might do: { first: 10 } on the query level, but actually get 20 records.

Side-note: this might break sorting.

Use-case 3: Merge root-operation, while union the responses

Status: ❓

flowchart TB
    linkStyle default interpolate basis
    c1[(Chain 1)]-->t1
    c2[(Chain 2)]-->t2

    subgraph "Subgraph A"
    t1("type Something")
    end

    subgraph "Subgraph B"
    t2("type Something")
    end

    t1---ccc
    t2---ccc
    ccc["Client-side composition"]---gc["Graph Client"]

    subgraph m["Composed Schema"]
    t3("type A_Something")
    t4("type B_Something")
    t5("union Something = A_Something | B_Something")
    t6("type Query {\n something: [Something!]! \n}")
    end

    gc---m
Loading

Similar to use-case number 2, but in this case the user might want to be able to distinguish where the record came from (origin of the Subgraph). In this case, we can create a GraphQL union that points to both types, and then user can do at query level:

query something($t: String) {
   something(t: $t) { # this will return a list of Something, originated on both A and B Subgraphs
      __typename # will be A_Something or B_Something  
     ... on A_Something {
       id
     } 
     ... on B_Something {
       id
     } 
   }
}

Or, actually, if we are using GraphQL interface, it can be:

query something($t: String) {
   something(t: $t) { # this will return a list of Something, originated on both A and B Subgraphs
      __typename # will be A_Something or B_Something  
       id
   }
}

Huge amount of redudant code required to satisfy cross chain needs

Here's an example of a single product which is being launched a number of chains.

Each chain requires:

  • 3 graphql files (identical, besides query names & prefix)
  • 3 sources (identical, besides source & prefix)

99% of this code appears to be redundant, and leads us towards having these huge if/else statements in our API because there seems to be no clean way to query per chain as we did in the past.

Previously we would be able to share the same API with a simple function mapping chainId => source, per convention when dealing with chain agnostic APIs.

e.g. getPairs(chainId) - all code remains the same except the endpoint...

Unfortunately this is now much more complex and cumbersome than ever before, at least in the way we're using it.

For this specific user case we should really need to specify nothing more than the additional sources. Maybe there's some human error in the way we are doing things here:

https://github.com/sushiswap/sushiswap/tree/feature/sushixswap/packages/graph-client/queries
https://github.com/sushiswap/sushiswap/tree/feature/sushixswap/packages/graph-client/sources
https://github.com/sushiswap/sushiswap/blob/feature/sushixswap/apps/furo/lib/graph.ts

This is probably one of the most simple examples of our use cases, a single product deployed on multiple chains.

Would love to hear your thoughts on this, and where we're going wrong.

Generated Graph SDK does not throw on Errors

With the generated graph client, if we have: result = await graphClient.SomeQuery({ ...variables }). It seems like the SomeQuery(...) call does not throw when the graph responds with:

{
  "errors": [...]
}

Is this expected behavior? One can easily test this by passing a ethers.BigNumber as a variable to a test query

Auto Pagination broken

When trying to do a simple query a formatting error is returned. The query runs fine in the cloud & the error only occurs locally when the autoPagination transformation is included .graphclientrc.yml.

image

Decentralized network: parallelism / race

Due to the nature of the decentralized network, and the ability/need to define fallbacks / alternative sources for the same schema, in some cases it might be useful to define a "race" between two indexers, and use the response from the indexer that replies first.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Warning

These dependencies are deprecated:

Datasource Name Replacement PR?
npm @babel/plugin-proposal-class-properties Unavailable

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): lock file maintenance

Detected dependencies

github-actions
.github/workflows/ci.yml
  • actions/checkout v4
.github/workflows/pr.yml
.github/workflows/release.yml
npm
examples/apollo/package.json
  • @apollo/client 3.11.4
  • graphql 16.9.0
  • react 18.3.1
  • react-dom 18.3.1
  • @types/react 18.3.3
  • @types/react-dom 18.3.0
  • @vitejs/plugin-react 4.3.1
  • typescript 5.5.4
  • vite 5.4.0
examples/auto-pagination-multiple-sources/package.json
  • concurrently 8.2.2
  • graphql 16.9.0
  • nodemon 3.1.4
  • ts-node 10.9.2
  • typescript 5.5.4
  • @types/node 20.14.15
examples/composition/package.json
  • @graphql-mesh/transform-rename ^0.100.0
  • concurrently ^8.0.1
  • graphql ^16.6.0
  • nodemon ^3.0.0
  • ts-node ^10.9.1
  • typescript ^5.0.4
  • @types/node ^20.0.0
examples/cross-chain-extension/package.json
  • graphql 16.9.0
examples/cross-chain-sdk/package.json
  • graphql 16.9.0
examples/execute/package.json
  • graphql 16.9.0
  • react 18.3.1
  • react-dom 18.3.1
  • @types/react 18.3.3
  • @types/react-dom 18.3.0
  • @vitejs/plugin-react 4.3.1
  • typescript 5.5.4
  • vite 5.4.0
examples/javascript-cjs/package.json
  • graphql 16.9.0
examples/javascript-esm/package.json
  • graphql 16.9.0
examples/live-queries/package.json
  • graphql 16.9.0
examples/nextjs/package.json
  • next 14.2.5
  • react 18.3.1
  • react-dom 18.3.1
  • @types/node 20.14.15
  • @types/react 18.3.3
  • eslint 9.9.0
  • eslint-config-next 14.2.5
  • typescript 5.5.4
examples/node/package.json
  • concurrently 8.2.2
  • graphql 16.9.0
  • nodemon 3.1.4
  • ts-node 10.9.2
  • typescript 5.5.4
  • @types/node 20.14.15
examples/react-query/package.json
  • @tanstack/react-query 5.51.23
  • graphql 16.9.0
  • react 18.3.1
  • react-dom 18.3.1
  • @types/react 18.3.3
  • @types/react-dom 18.3.0
  • @vitejs/plugin-react 4.3.1
  • typescript 5.5.4
  • vite 5.4.0
examples/transforms/package.json
  • @graphql-mesh/transform-prefix ^0.100.0
  • graphql ^16.6.0
examples/urql-live-query/package.json
  • graphql 16.9.0
  • react 18.3.1
  • react-dom 18.3.1
  • urql 4.1.0
  • @types/react 18.3.3
  • @types/react-dom 18.3.0
  • @vitejs/plugin-react 4.3.1
  • typescript 5.5.4
  • vite 5.4.0
examples/urql/package.json
  • graphql 16.9.0
  • react 18.3.1
  • react-dom 18.3.1
  • urql 4.1.0
  • @types/react 18.3.3
  • @types/react-dom 18.3.0
  • @vitejs/plugin-react 4.3.1
  • typescript 5.5.4
  • vite 5.4.0
package.json
  • @babel/core 7.25.2
  • @babel/plugin-proposal-class-properties 7.18.6
  • @babel/plugin-proposal-decorators 7.24.7
  • @babel/preset-env 7.25.3
  • @babel/preset-typescript 7.24.7
  • @changesets/cli 2.27.7
  • @changesets/changelog-github 0.5.0
  • @types/babel__core 7.20.5
  • @types/babel__preset-env 7.9.7
  • @types/jest 29.5.12
  • babel-jest 29.7.0
  • babel-plugin-parameter-decorator 1.0.16
  • babel-plugin-transform-typescript-metadata 0.3.2
  • bob-the-bundler 7.0.1
  • graphql 16.9.0
  • husky 9.1.4
  • jest 29.7.0
  • prettier 3.3.3
  • pretty-quick 4.0.0
  • rimraf 6.0.1
  • typescript 5.5.4
  • weak-napi 2.0.2
  • ts-jest 29.2.4
  • graphql 16.9.0
packages/add-source-name/package.json
  • lodash ^4.17.21
  • tslib ^2.4.0
  • @types/lodash ^4.14.194
  • graphql ^15.2.0 || ^16.0.0
  • @graphql-tools/delegate ^9.0.32 || ^10.0.0
  • @graphql-tools/wrap ^9.4.2 || ^10.0.0
  • @graphql-tools/utils ^9.2.1 || ^10.0.0
  • @graphql-mesh/types ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 || ^0.94.0 || ^0.97.0 || ^0.98.0 || ^0.99.0 || ^0.100.0
  • node >=16.0.0
packages/apollo/package.json
  • @graphql-mesh/apollo-link ^0.101.0
  • tslib ^2.4.0
  • graphql ^15.2.0 || ^16.0.0
  • @apollo/client ^3.5.0
  • node >=16.0.0
packages/auto-pagination/package.json
  • lodash ^4.17.21
  • tslib ^2.4.0
  • @types/lodash ^4.14.194
  • @graphql-mesh/transform-prefix ^0.100.0
  • graphql ^15.2.0 || ^16.0.0
  • @graphql-tools/delegate ^9.0.32 || ^10.0.0
  • @graphql-tools/wrap ^9.4.2 || ^10.0.0
  • @graphql-tools/utils ^9.2.1 || ^10.0.0
  • @graphql-mesh/types ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 || ^0.94.0 || ^0.97.0 || ^0.98.0 || ^0.99.0 || ^0.100.0
  • node >=16.0.0
packages/auto-type-merging/package.json
  • @graphql-mesh/transform-type-merging ^0.100.0
  • tslib ^2.4.0
  • graphql ^15.2.0 || ^16.0.0
  • @graphql-tools/delegate ^9.0.32 || ^10.0.0
  • @graphql-mesh/types ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 || ^0.94.0 || ^0.97.0 || ^0.98.0 || ^0.99.0 || ^0.100.0
  • node >=16.0.0
packages/block-tracking/package.json
  • @graphql-tools/utils ^10.0.0
  • @graphql-mesh/fusion-runtime ^0.6.0
  • tslib ^2.4.0
  • graphql ^15.2.0 || ^16.0.0
  • @graphql-tools/delegate ^9.0.32 || ^10.0.0
  • node >=16.0.0
packages/cli/package.json
  • @graphql-mesh/cli ^0.93.0
  • @graphql-mesh/graphql ^0.100.0
  • tslib ^2.4.0
  • graphql ^16.6.0
  • graphql ^15.2.0 || ^16.0.0
  • node >=16.0.0
packages/polling-live/package.json
  • @repeaterjs/repeater ^3.0.4
  • tslib ^2.4.0
  • graphql ^15.2.0 || ^16.0.0
  • @graphql-tools/merge ^8.3.14 || ^9.0.0
  • @envelop/core ^2.4.2 || ^3.0.0 || ^4.0.0 || ^5.0.0
  • node >=16.0.0
packages/urql/package.json
  • @graphql-mesh/urql-exchange ^0.101.0
  • tslib ^2.4.0
  • graphql ^15.2.0 || ^16.0.0
  • @urql/core ^2.4.0 || ^3.0.0 || ^4.0.0 || ^5.0.0
  • node >=16.0.0

  • Check this box to trigger a request for Renovate to run again on this repository

Make auto-pagination use Relay's Cursor Connection Spec

The Cursor Connection Spec has become a de-facto standard in the larger GraphQL ecosystem, as far as I can tell. I first learned about it when using Gatsby, which uses the modified version of the Cursor Connection Spec advocated in this blog post: GraphQL Pagination best practices: Using Edges vs Nodes in Connections (this is my favorite post I've seen on the topic, as it is brief and contains links to other resources).

I have found graph-client's auto pagination difficult to work with on the client side, and would enjoy the flexibility and power offered by the Cursor Connection Spec. I also think this could be built in a backwards-compatible way, adding additional ways to access paginated data if people want to, without removing the method of pagination they're already using.

If y'all agree this is worth trying, I'll have time in my schedule in mid-September to try to help add it.

Custom resolver in cross chain mode fail if one source fails

Using the cross-chain-extensions example which has been very useful, but If I have 20 sources for the same subgraph across networks and I'm querying them as a whole, if one endpoint fails, it seems the entire thing fails.

I wonder if there's a way to make this more robust in the sense that it can deal with some of the endpoints failing but still return data for the others, or else the weakest link will always be a problem in cross chain mode.

Different sources based on chainId

Hi, is there a way to change source urls in runtime? Either automatically or by providing a variable is fine.
Multiple clients is also fine i guess?
But for sure need an option to do that.

Sorry if it's obvious, but just found it and try to check if we can use it.

Use graph client with docker network based graph node

We are using a graph node as part of a fully-local setup:

https://github.com/connext/nxtp/blob/a0eb052ac0abb8b83821005f1c44dd8d2448a1fb/docker-compose.chains.yaml#L21

I want to be able to generate a graph client that can be used on resources within this docker network that talk to the graph node. The config for this would look like this:

https://github.com/connext/nxtp/blob/e0519f79172025770047e3aed5c7fa5796f6d56c/packages/adapters/subgraph/.graphclientrc.yml#L140

However I am not able to easily generate the client for this. When I try, it fails because the host cannot access this endpoint. If I use localhost, the container cannot access the graph node because it's not on localhost from the container's point of view. The workaround I used is to spin up a temporary container within the network that can do this. However this is not a feasible option for our devs to use.

Is there another way to generate the schema from a different URL but have the client use the provided URL for the queries?

Node v14 support

It seems that we're locked into depedencies which will only work on Node v16. Unfortuantely even AWS isn't supporting v16 yet which means we're blocked from using this. I have done some trial and error to find v14 compatbile options, but unable to find any compatible combination so far.

Next.js example

Thanks for sharing these. I've tried using various of the examples provided in a Next.js application, I was hoping to use Apollo but failed to get it running smoothly. It would be useful to see some examples if you have the time to do that :)

Decentralised network: number_gte

When polling subgraphs on the network via the gateway, clients might receive responses from indexers at different blocks for different queries. In particular it is not desirable for a client to travel backwards in time (when polling the subgraph for updates, for example).

Developers can ensure that they don't go backwards by passing a number_gte argument to the block parameter, as follows:

async function updateProtocolPaused() {
  // It's ok to start with minBlock at 0. The query will be served
  // using the latest block available. Setting minBlock to 0 is the
  // same as leaving out that argument.
  let minBlock = 0

  for (;;) {
    // Schedule a promise that will be ready once
    // the next Ethereum block will likely be available.
    const nextBlock = new Promise((f) => {
      setTimeout(f, 14000)
    })

    const query = `
        {
            protocol(block: { number_gte: ${minBlock} }  id: "0") {
              paused
            }
            _meta {
                block {
                    number
                }
            }
        }`

    const response = await graphql(query)
    minBlock = response._meta.block.number

    // TODO: Do something with the response data here instead of logging it.
    console.log(response.protocol.paused)

    // Sleep to wait for the next block
    await nextBlock
  }
}

Docs

Graph Client should offer this as an easy option for developers, so they don't have to implement the logic themselves.

Failed to generate schema error

I can't seem to get a client generated from our graph endpoint.

package.json

{
  "scripts": {
    "build": "graphclient build"
  },
  "devDependencies": {
    "@graphprotocol/client-cli": "^2.0.1"
  }
}

.graphclientrc.yaml

sources:
  - name: valist
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/valist-io/valist

Error output below.

💡 GraphClient Cleaning existing artifacts
💡 GraphClient Reading the configuration
💡 GraphClient Generating the unified schema
💥 GraphClient - valist Failed to generate the schema Error: Failed to fetch introspection from https://api.thegraph.com/subgraphs/name/valist-io/valist: TypeError: Request with GET/HEAD method cannot have body.
    at new Request (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/undici/lib/fetch/request.js:437:13)
    at Agent.fetch (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/undici/lib/fetch/index.js:114:21)
    at fetch (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/undici/index.js:90:22)
    at fetch (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/cross-undici-fetch/dist/create-node-ponyfill.js:130:16)
    at /Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/fetchache/index.js:21:36
    at async Object.getWithSet (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/@graphql-mesh/store/index.js:155:38)
    at GraphQLHandler.getMeshSource (/Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/@graphql-mesh/graphql/index.js:295:23)
    at async /Users/keenan/Projects/valist/valist/valist-js/packages/valist-graph-client/node_modules/@graphql-mesh/runtime/index.js:219:28
💥 GraphClient Error: Schemas couldn't be generated successfully. Check for the logs by running Mesh with DEBUG=1 e...

AggregateError

GM

I was going fine incorporating a GraphQL document into my next project.

I encountered this error when I tried to add a second GraphQL document, and still encounter it after:

  • Reverting changes
  • Clearing .graphclient, node_modules and reinstalling & building
  • Creating a new branch from main with the original working branch

Error

yarn run v1.22.19
$ graphclient build
💡 GraphClient Cleaning existing artifacts
💡 GraphClient Reading the configuration
💡 GraphClient Generating the unified schema
💡 GraphClient Generating artifacts
💡 GraphClient Generating index file in TypeScript
💥 GraphClient AggregateError: GraphQL Document Validation failed with 1 errors;
  Error 0: GraphQLDocumentError: V...
error Command failed with exit code 1.

Client Manifest

sources:
  - name: InsertName
    handler:
      graphql:
        endpoint: https://api.thegraph.com/subgraphs/name/InsertName/InsertName-mumbai
plugins:
  - pollingLive:
      defaultInterval: 3000

documents:
  - ./documents/getQuiz.graphql

Document

query GetQuiz($id: String) @live(interval: 3000) {
  quiz(id: $id) {
    ..... query 
  }
}

Any ideas on the inconsistent build & error produced?

EDIT-

It built fine with this document, perhaps it only propogated the error once I built it the second time, because it had the types generated and could do a type check on the subsequent builds?

Fetch strategy: `highestValue` (to implement most synced)

In cases where you might have two sources and you will query both, and only take the most synced (highest block), this might be useful.

To achieve that, we need to make sure to add __meta and block information to outgoing requests:

            _meta {
                block {
                    number
                }
            }

and then check the response based on _meta.block.number on both responses?

graph LR;
    subgraph most-synced
    req(Outgoing Query)-->sA[Subgraph A]; 
    sA-->d{MostSyncedStrategy};
    d-->s1[Source 1]; 
    d-->s2[Source 2]; 
    s1-->synced["process"]
    s2-->synced
    synced-->|"max(_meta.block_number)"|d
    end
Loading

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.