From 85a87308a7304954371983beb8f16b971e4beedb Mon Sep 17 00:00:00 2001 From: Lex Buistov Date: Mon, 22 Apr 2024 17:33:21 -0400 Subject: [PATCH] Addresses #261 and developer guild issue 30 (#508) --- .../indexer/run_publish/query/subscription.md | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/docs/indexer/run_publish/query/subscription.md b/docs/indexer/run_publish/query/subscription.md index 3a6f12b9904..356997156ec 100644 --- a/docs/indexer/run_publish/query/subscription.md +++ b/docs/indexer/run_publish/query/subscription.md @@ -67,6 +67,168 @@ subscription { Note that the `mutation` filter can be one of `INSERT`, `UPDATE` or `DELETE`. + +## Server-side Implementation with Apollo Server +First, let's set up the server-side. This includes defining the subscription type, event type, setting up a publish/subscribe mechanism (using `PubSub` from `graphql-subscriptions`), and adding a resolver for the subscription. + +Below is the basic, minimal example in Node.js. Refer to the official documentation for other supported technologies (for example, [Kotlin](https://www.apollographql.com/docs/kotlin/essentials/subscriptions) or [Swift](https://www.apollographql.com/docs/ios/tutorial/tutorial-subscriptions/)) + +```javascript +const { ApolloServer, gql, PubSub } = require('apollo-server'); +const pubsub = new PubSub(); + +// GraphQL type definitions +const typeDefs = gql` + type Balances { + id: ID! + amount: Int! + } + + type BalanceUpdate { + id: ID! + mutation_type: String! + _entity: String! + amount: Int! + } + + type Subscription { + balances(id: ID!, mutation: String!): BalanceUpdate + } +`; + +// Resolvers define the technique for fetching the types defined in the schema +const resolvers = { + Subscription: { + balances: { + subscribe: (_, { id, mutation }) => { + const channel = `${mutation}_${id}`; + return pubsub.asyncIterator(channel); + }, + }, + }, +}; + +// Create the Apollo Server instance +const server = new ApolloServer({ typeDefs, resolvers }); + +// Start the server +server.listen().then(({ url }) => { + console.log(`Server ready at ${url}`); +}); +``` + +You would need to have the logic to publish events to this subscription, typically in your mutation resolvers or anywhere the balance changes. + +Example publishing event: + +```javascript + +// Somewhere in your balance update business logic... +pubsub.publish(`UPDATE_${accountId}`, { + balances: { + id: accountId, + mutation_type: 'UPDATE', + _entity: 'Balances', + amount: newBalance, // assume newBalance is the updated balance + } +}); +``` + +Note that this example does not include error handling or authentication/authorization, which are essential for production applications. + +### Client-side Implementation with React and Apollo Client + +Now, let's move on to the client side where we will be using React and Apollo Client. We'll create a component that subscribes to the balance updates for a specific account. + +First, ensure your project has the required dependencies: + +```bash +npm install @apollo/client graphql +``` + +Then, you can create a React component like this: + +```javascript +import React, { useEffect } from 'react'; +import { useSubscription, gql } from '@apollo/client'; + +const BALANCES_SUBSCRIPTION = gql` + subscription BalancesSubscription($id: ID!, $mutation: String!) { + balances(id: $id, mutation: $mutation) { + id + mutation_type + _entity + amount + } + } +`; + +const BalanceUpdates = ({ accountId }) => { + const { data, loading, error } = useSubscription(BALANCES_SUBSCRIPTION, { + variables: { id: accountId, mutation: "UPDATE" }, + }); + + useEffect(() => { + if (data) { + console.log('Received data:', data); + } + }, [data]); + + if (loading) return

Subscription is loading...

; + if (error) return

Error: {error.message}

; + + return ( +
+

Got balance update for wallet {accountId}

+ {data &&

New balance: {JSON.stringify(data.balances.amount)}

} +
+ ); +}; + +export default BalanceUpdates; +``` + +This React component uses `useSubscription` from Apollo Client to subscribe to the balance updates. Make sure your Apollo Client is properly configured to connect to your GraphQL server, and it supports WebSocket for subscriptions. + +To connect to the server, typically, you would set up `ApolloClient` with something like this: + +```javascript +import { ApolloClient, InMemoryCache, HttpLink, split } from '@apollo/client'; +import { WebSocketLink } from '@apollo/client/link/ws'; +import { getMainDefinition } from '@apollo/client/utilities'; + +const httpLink = new HttpLink({ + uri: 'http://your-server.com/graphql', +}); + +const wsLink = new WebSocketLink({ + uri: 'ws://your-server.com/graphql', + options: { + reconnect: true + } +}); + +const splitLink = split( + ({ query }) => { + const definition = getMainDefinition(query); + return ( + definition.kind === 'OperationDefinition' && + definition.operation === 'subscription' + ); + }, + wsLink, + httpLink, +); + +const client = new ApolloClient({ + link: splitLink, + cache: new InMemoryCache() +}); + +``` + +This setup uses HTTP for queries and mutations, and WebSocket for subscriptions, switching automatically based on the operation type. For production readiness, consider adding logic in the client component to handle unsubscription or cleanup when the component unmounts or when the user navigates away. + ::: warning Important Please note that you must enable the `--subscription` flag on both the node and query service in order to use these functions. :::