diff --git a/.changeset/config.json b/.changeset/config.json index 436c8424..68679088 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -14,7 +14,8 @@ "javascript-cjs-example", "react-query-example", "nextjs-example", - "cross-chain-example" + "cross-chain-sdk", + "cross-chain-extension" ], "access": "public", "baseBranch": "main" diff --git a/README.md b/README.md index 2bd4d164..bb3c6098 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This library is intended to simplify the network aspect of data consumption for | ✅ | 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 merging | Merge similiar Subgraphs into a single response | +| ✅ | 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](./packages/block-tracking/README.md) | tracking block numbers [as described here](https://thegraph.com/docs/en/developer/distributed-systems/#polling-for-updated-data) | @@ -144,7 +144,9 @@ You can also refer to [examples directory in this repo](./examples/), for more a - [Integration with NextJS and TypeScript](./examples/nextjs/) - [Integration with Apollo-Client and React](./examples/apollo/) - [Integration with React-Query](./examples/react-query/) -- [Cross-chain merging (same Subgraph, different chains)](./examples/cross-chain/) +- _Cross-chain merging (same Subgraph, different chains)_ +- - [Parallel SDK calls](./examples/cross-chain-sdk/) +- - [Parallel internal calls with schema extensions](./examples/cross-chain-extension/) - [Customize execution with Transforms (auto-pagination and auto-block-tracking)](./examples/transforms/) ### Advanced Examples/Features diff --git a/examples/cross-chain/.gitignore b/examples/cross-chain-extension/.gitignore similarity index 100% rename from examples/cross-chain/.gitignore rename to examples/cross-chain-extension/.gitignore diff --git a/examples/cross-chain-extension/.graphclientrc.yml b/examples/cross-chain-extension/.graphclientrc.yml new file mode 100644 index 00000000..bd665c91 --- /dev/null +++ b/examples/cross-chain-extension/.graphclientrc.yml @@ -0,0 +1,24 @@ +sources: + - name: Sushiswap + handler: + graphql: + # Default value is bentobox-avalance + # This is needed for the initial introspection on buildtime + endpoint: https://api.thegraph.com/subgraphs/name/matthewlilley/{context.chainName:bentobox-avalanche} + +additionalTypeDefs: | + extend type Rebase { + chainName: String + } + extend type Query { + crossRebases(first: Int!, chainNames: [String!]!): [Rebase!]! + } + +additionalResolvers: + - ./src/resolvers.ts + +documents: + - ./example-query.graphql + +codegen: + contextType: 'MeshContext & { chainName: string }' diff --git a/examples/cross-chain-extension/README.md b/examples/cross-chain-extension/README.md new file mode 100644 index 00000000..4173d0ce --- /dev/null +++ b/examples/cross-chain-extension/README.md @@ -0,0 +1,54 @@ +### The Graph Client / NodeJS (TS) + +This examples integrates The Graph Client with NodeJS/TypeScript usage, with 2 Subgraphs that has similar GraphQL schemas. The goal of this example is to do requests to different subgraphs with dynamic context values + +The example here is using the following tools/concepts: + +- NodeJS + TypeScript +- The Graph Client CLI for generating artifacts +- [Schema-stitching resolvers extension](https://www.graphql-mesh.com/docs/guides/extending-unified-schema) + +```mermaid +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 { + # ... rest of the fields + chainId: String! + }") + end + + gc---m +``` + +### Getting Started + +To run this example, make sure to install the dependencies in the root of the monorepo, build the client locally, and then run this example: + +``` +# In the root directory +$ yarn install +$ yarn build +$ cd examples/cross-chain-extension +$ yarn build-client +$ yarn start +``` + +### DevTools + +You can also run The Graph Client DevTools by running: `yarn graphiql`. diff --git a/examples/cross-chain-extension/example-query.graphql b/examples/cross-chain-extension/example-query.graphql new file mode 100644 index 00000000..3748f849 --- /dev/null +++ b/examples/cross-chain-extension/example-query.graphql @@ -0,0 +1,8 @@ +query CrossRebasesExample($chainNames: [String!]!) { + crossRebases(first: 2, chainNames: $chainNames) { + chainName + id + base + elastic + } +} diff --git a/examples/cross-chain-extension/package.json b/examples/cross-chain-extension/package.json new file mode 100644 index 00000000..e9c828e0 --- /dev/null +++ b/examples/cross-chain-extension/package.json @@ -0,0 +1,16 @@ +{ + "name": "cross-chain-extension", + "private": true, + "version": "0.0.0", + "scripts": { + "start": "ts-node --transpileOnly src/index.ts", + "build-client": "graphclient build", + "check": "tsc --pretty --noEmit", + "graphiql": "graphclient serve-dev" + }, + "dependencies": { + "@graphprotocol/client-add-source-name": "1.0.1", + "@graphprotocol/client-cli": "2.0.0", + "graphql": "16.3.0" + } +} diff --git a/examples/cross-chain-extension/src/index.ts b/examples/cross-chain-extension/src/index.ts new file mode 100644 index 00000000..b5514540 --- /dev/null +++ b/examples/cross-chain-extension/src/index.ts @@ -0,0 +1,17 @@ +import { getBuiltGraphSDK } from '../.graphclient' + +// Cross Chain Call from additional type definitions and resolvers +async function main() { + const sdk = getBuiltGraphSDK() + // Second parameter is the context value + const results = await sdk.CrossRebasesExample({ + chainNames: ['bentobox-avalanche', 'bentobox-bsc'], + }) + + console.table(results.crossRebases) +} + +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/examples/cross-chain-extension/src/resolvers.ts b/examples/cross-chain-extension/src/resolvers.ts new file mode 100644 index 00000000..11d55e8c --- /dev/null +++ b/examples/cross-chain-extension/src/resolvers.ts @@ -0,0 +1,30 @@ +import { Resolvers, MeshContext } from '../.graphclient' + +export const resolvers: Resolvers = { + Rebase: { + // chainName can exist already in root as we pass it in the other resolver + chainName: (root, args, context, info) => root.chainName || context.chainName || 'bentobox-avalanche', // The value we provide in the config + }, + Query: { + crossRebases: async (root, args, context, info) => + Promise.all( + args.chainNames.map((chainName) => + context.Sushiswap.Query.rebases({ + root, + args, + context: { + ...context, + chainName, + }, + info, + }).then((rebases) => + // We send chainName here so we can take it in the resolver above + rebases.map((rebase) => ({ + ...rebase, + chainName, + })), + ), + ), + ).then((allRebases) => allRebases.flat()), + }, +} diff --git a/examples/cross-chain-extension/tsconfig.json b/examples/cross-chain-extension/tsconfig.json new file mode 100644 index 00000000..5a4a79c8 --- /dev/null +++ b/examples/cross-chain-extension/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "module": "commonjs", + "sourceMap": true, + "lib": ["esnext", "DOM", "DOM.Iterable"], + "allowSyntheticDefaultImports": true + }, + "files": ["src/index.ts"] +} diff --git a/examples/cross-chain-sdk/.gitignore b/examples/cross-chain-sdk/.gitignore new file mode 100644 index 00000000..e6c8309a --- /dev/null +++ b/examples/cross-chain-sdk/.gitignore @@ -0,0 +1 @@ +.graphclient \ No newline at end of file diff --git a/examples/cross-chain-sdk/.graphclientrc.yml b/examples/cross-chain-sdk/.graphclientrc.yml new file mode 100644 index 00000000..925055a9 --- /dev/null +++ b/examples/cross-chain-sdk/.graphclientrc.yml @@ -0,0 +1,21 @@ +sources: + - name: Sushiswap + handler: + graphql: + # Default value is bentobox-avalance + # This is needed for the initial introspection on buildtime + endpoint: https://api.thegraph.com/subgraphs/name/matthewlilley/{context.chainName:bentobox-avalanche} + +additionalTypeDefs: | + extend type Rebase { + chainName: String! + } + +additionalResolvers: + - ./src/resolvers.ts + +documents: + - ./example-query.graphql + +codegen: + contextType: 'MeshContext & { chainName: string }' diff --git a/examples/cross-chain-sdk/README.md b/examples/cross-chain-sdk/README.md new file mode 100644 index 00000000..5b1d6d08 --- /dev/null +++ b/examples/cross-chain-sdk/README.md @@ -0,0 +1,54 @@ +### The Graph Client / NodeJS (TS) + +This examples integrates The Graph Client with NodeJS/TypeScript usage, with 2 Subgraphs that has similar GraphQL schemas. The goal of this example is to do requests to different subgraphs with dynamic context values + +The example here is using the following tools/concepts: + +- NodeJS + TypeScript +- The Graph Client CLI for generating artifacts +- [Schema-stitching resolvers extension](https://www.graphql-mesh.com/docs/guides/extending-unified-schema) + +```mermaid +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 { + # ... rest of the fields + chainId: String! + }") + end + + gc---m +``` + +### Getting Started + +To run this example, make sure to install the dependencies in the root of the monorepo, build the client locally, and then run this example: + +``` +# In the root directory +$ yarn install +$ yarn build +$ cd examples/cross-chain-sdk +$ yarn build-client +$ yarn start +``` + +### DevTools + +You can also run The Graph Client DevTools by running: `yarn graphiql`. diff --git a/examples/cross-chain-sdk/example-query.graphql b/examples/cross-chain-sdk/example-query.graphql new file mode 100644 index 00000000..658deca8 --- /dev/null +++ b/examples/cross-chain-sdk/example-query.graphql @@ -0,0 +1,8 @@ +query RebasesExample { + rebases(first: 2) { + chainName + id + base + elastic + } +} diff --git a/examples/cross-chain/package.json b/examples/cross-chain-sdk/package.json similarity index 92% rename from examples/cross-chain/package.json rename to examples/cross-chain-sdk/package.json index aa0d6c58..79c95544 100644 --- a/examples/cross-chain/package.json +++ b/examples/cross-chain-sdk/package.json @@ -1,5 +1,5 @@ { - "name": "cross-chain-example", + "name": "cross-chain-sdk", "private": true, "version": "0.0.0", "scripts": { diff --git a/examples/cross-chain-sdk/src/index.ts b/examples/cross-chain-sdk/src/index.ts new file mode 100644 index 00000000..5535ec0c --- /dev/null +++ b/examples/cross-chain-sdk/src/index.ts @@ -0,0 +1,26 @@ +import { getBuiltGraphSDK } from '../.graphclient' + +async function main() { + const sdk = getBuiltGraphSDK({ + chainName: 'bentobox-bsc', // We can provide a default value here + }) + // Second parameter is the context value + const results = await Promise.all([ + sdk + .RebasesExample( + {}, + { + chainName: 'bentobox-avalanche', // And override it in here + }, + ) + .then((data) => data.rebases), + sdk.RebasesExample().then((data) => data.rebases), + ]) + + console.table(results.flat()) +} + +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/examples/cross-chain-sdk/src/resolvers.ts b/examples/cross-chain-sdk/src/resolvers.ts new file mode 100644 index 00000000..959f2512 --- /dev/null +++ b/examples/cross-chain-sdk/src/resolvers.ts @@ -0,0 +1,7 @@ +import { Resolvers, MeshContext } from '../.graphclient' + +export const resolvers: Resolvers = { + Rebase: { + chainName: (root, args, context, info) => context.chainName || 'bentobox-avalanche', // The value we provide in the config + }, +} diff --git a/examples/cross-chain-sdk/tsconfig.json b/examples/cross-chain-sdk/tsconfig.json new file mode 100644 index 00000000..5a4a79c8 --- /dev/null +++ b/examples/cross-chain-sdk/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "esnext", + "moduleResolution": "node", + "module": "commonjs", + "sourceMap": true, + "lib": ["esnext", "DOM", "DOM.Iterable"], + "allowSyntheticDefaultImports": true + }, + "files": ["src/index.ts"] +} diff --git a/examples/cross-chain/.graphclientrc.yml b/examples/cross-chain/.graphclientrc.yml deleted file mode 100644 index cd3149ef..00000000 --- a/examples/cross-chain/.graphclientrc.yml +++ /dev/null @@ -1,39 +0,0 @@ -sources: - - name: Chain1 - handler: - graphql: - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2 - transforms: - - addSourceName: true - - prefix: - value: Chain1_ - includeRootOperations: true - includeTypes: false - - name: Chain2 - handler: - graphql: - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2 - transforms: - - addSourceName: true - - prefix: - value: Chain2_ - includeRootOperations: true - includeTypes: false - -additionalTypeDefs: | - extend type Query { - pairs( - first: Int = 100, - orderBy: Pair_orderBy, - orderDirection: OrderDirection, - skip: Int = 0, - subgraphError: _SubgraphErrorPolicy_! = deny, - where: Pair_filter - ): [Pair!]! - } - -additionalResolvers: - - ./src/pairs.ts - -documents: - - ./src/example-query.graphql diff --git a/examples/cross-chain/README.md b/examples/cross-chain/README.md deleted file mode 100644 index 082bc426..00000000 --- a/examples/cross-chain/README.md +++ /dev/null @@ -1,95 +0,0 @@ -### The Graph Client / NodeJS (TS) - -This examples integrates The Graph Client with NodeJS/TypeScript usage, with 2 Subgraphs that has similar GraphQL schemas. The goal of this example is to merge the two responses in a custom way, so the GraphQL layer does all aggregations needed. - -The example here is using the following tools/concepts: - -- NodeJS + TypeScript -- The Graph Client CLI for generating artifacts -- [Schema-stitching type-merging](https://www.graphql-tools.com/docs/schema-stitching/stitch-type-merging) -- [Schema-stitching resolvers extension](https://www.graphql-mesh.com/docs/guides/extending-unified-schema) -- Client-side Compostion (more than 1 source) - -```mermaid -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 { - # ... rest of the fields - sourceName: String! - }") - end - - gc---m -``` - -### Getting Started - -To run this example, make sure to install the dependencies in the root of the monorepo, build the client locally, and then run this example: - -``` -# In the root directory -$ yarn install -$ yarn build -$ cd examples/cross-chain -$ yarn build-client -$ yarn start -``` - -### Using `addSourceName` - -Graph-Client comes with a built-in utility for injecting the name of the source as part of each `type` definition. This is useful in case you need to know where each object came from, and distinguish the Subgraph created the object. - -To use this feature, add `transforms` to each source: - -```yaml -sources: - - name: Chain1 - handler: - graphql: - endpoint: https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2 - transforms: - - addSourceName: true # Add this -``` - -Now, in every GraphQL `type`, you'll have a new field added (`sourceName: String!`), that represent the `name` defined for that source. - -You can include that in your GraphQL query, and use the field as a way to distinguish the origin of the object: - -```graphql -query crossChainPairs { - pairs(subgraphError: allow) { - id - sourceName - token0 { - id - } - token1 { - id - } - } -} -``` - -### DevTools - -You can also run The Graph Client DevTools by running: `yarn graphiql`. - -``` - -``` diff --git a/examples/cross-chain/src/example-query.graphql b/examples/cross-chain/src/example-query.graphql deleted file mode 100644 index 0b151ba1..00000000 --- a/examples/cross-chain/src/example-query.graphql +++ /dev/null @@ -1,12 +0,0 @@ -query crossChainPairs { - pairs(subgraphError: allow) { - id - token0 { - id - } - token1 { - id - } - sourceName - } -} diff --git a/examples/cross-chain/src/pairs.ts b/examples/cross-chain/src/pairs.ts deleted file mode 100644 index e25886b4..00000000 --- a/examples/cross-chain/src/pairs.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Resolvers } from '../.graphclient' - -export const resolvers: Resolvers = { - Query: { - async pairs(root, args, context, info) { - const responses = await Promise.all([ - context.Chain1.Query.Chain1_pairs({ - root, - args, - context, - info, - }), - context.Chain2.Query.Chain2_pairs({ - root, - args, - context, - info, - }), - ]) - return responses.flat() - }, - }, -}