Package Exports
- supagraph
- supagraph/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (supagraph) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
Supagraph
This repo contains a utility toolkit to index any RPC using subgraph-like mapping definitions backed by MongoDB/node-persist.
The constructed Entities are made available in the background to a schema-driven graphql-yoga instance via resolvers.
Tech Stack
We are depending on:
graphql-yogato create a GraphQL endpointethersto mapEventsto storedEntitiesmongodb/node-persistas a persistence layertypescript,eslint, andprettierto maintain coding standards
Documentation
Usage
Supagraph has been broken into two core stand alone pieces, sync and graphql, you might want to run either or both in order to index and/or query your data.
Getting started
To get started with supagraph, follow these steps:
Install the required dependencies:
$ pnpm add supagraph
Indexing contracts with supagraph sync
To create a new supagraph syncOp[] handler and keep the supagraph data Store up to date, follow these steps:
Import the necessary functions:
import { addSync, DB, Mongo, Store, sync } from "supagraph";
Configure a
dbengine to back theStore:// switch out the engine for development to avoid the mongo requirement locally Store.setEngine({ // name the connection name: SUPAGRAPH_NAME, // db is dependent on state db: // in production/production like environments we want to store mutations to mongo otherwise we can store them locally process.env.NODE_ENV === "development" && SUPAGRAPH_DEV_ENGINE ? // connect store to in-memory/node-persist store DB.create({ kv: {}, name: SUPAGRAPH_NAME }) : // connect store to MongoDB Mongo.create({ kv: {}, name: SUPAGRAPH_NAME, mutable: SUPAGRAPH_MUTABLE_ENTITIES, // if we want supagraph to produce a new entry for every event set this to `false` client: getMongodb(process.env.MONGODB_URI!), // getMongodb should return a MongoClient instance (imported from mongodb) }), });
Create a new
syncOp[]handler usingaddSync():const handler = addSync<T>( CONTRACT_EVT, CHAINS_PROVIDER, CONTRACT_START_BLOCK, CONTRACT_ADDRESS, CONTRACT_ABI, EVENT_HANDLER );
Retrieve an
entityfrom theStoreand update its properties inside an event handler:const handler = addSync<{ name: string; number: string }>( CONTRACT_EVT, CHAINS_PROVIDER, CONTRACT_START_BLOCK, CONTRACT_ADDRESS, CONTRACT_ABI, // handler code... async ({ name, number }, { tx, block }) => { let entity = await Store.get<{ id: string; name: string; number: string; owner: string; }>("Name", tx.from); entity.set("name", name); entity.set("number", number); entity.set("owner", tx.from); entity = await entity.save(); } );
Call the
sync()method in theGEThandler of a nextjsroute.tsdocument (or by any other means if runningsupagraphin a different environment):export async function GET() { const summary = await sync(); // await all new events to be processed and stored from all sync operations // Code for handling the response }
To keep a Vercel-hosted
supagraphinstance up to date, set acronjob in the project'svercel.jsonconfig to call the${sync_get_route}every minute (reqs per day===syncOps * 1440):# vercel.json { "crons": [ { "path": `${sync_get_route}`, "schedule": "* * * * *" } ] }Preview deployments will not run cron-jobs. Instead we can register an external cron with a service like cron-job.org to invoke the
${sync_get_route}against the same schedule*.*Note: the
dbwill lock betweensync()operations to enforce idempotent processing, therefore we only need to register one cron-job for each uniquesupagraphinstance (byname).
Querying indexed data with GraphQL
Construct a
schemawhich will be mapped 1:1 against theentitiesyou supply in the config:// Schemas follow GraphQL Schema Definition Language (SDL) const schema = ` # id = \`\${owner}\` type Name @entity { id: ID! name: String owner: Bytes number: BigNumber } `; // Entities must eventually resolve to an object of named entity arrays const entities = { Name: [ { id: "0", name: "grezle", owner: "0x0...", number: "0848293943825030", }, ], };
When using
supagraphto constructGraphQLendpoints from static data,createSupagraphcan be supplied a raw mapping of arrays ({[EntityName]: []}) as theentitiesprop, otherwise,entitiesshould be supplied as a validresolver:// import graphql factory and resolvers import { createSupagraph, memoryResolver, mongoResolver } from "supagraph"; // construct the graphql server instance const supagraph = createSupagraph({ // pass the schema (as a string) schema, // pass the entities as a resolver entities: // in development mode... (process.env.NODE_ENV === "development" && SUPAGRAPH_DEV_ENGINE) || // or if the mongodb uri isn't set... !process.env.MONGODB_URI ? // for development we can keep entities in node-persist and share between connections memoryResolver({ // name the database (in-memory/persisted on disk to .next dir) name: SUPAGRAPH_NAME, }) : // for production/production like we want to query mongo for results... mongoResolver({ // name the database (this defines the db name for mongodb - changing the name will create a new db) name: SUPAGRAPH_NAME, // connect to mongodb client: getMongodb(process.env.MONGODB_URI), // getMongodb should return a MongoClient instance (imported from mongodb) // if we want supagraph to produce a new entry for every event set this to `false` mutable: SUPAGRAPH_MUTABLE_ENTITIES, }), // the absolute path which will proxy supagraph.GET()/.POST() requests graphqlEndpoint: `graphql`, // the default query supplied to graphiql defaultQuery: ` { names { id name number owner } }`, });
The
supagraphinstance is ahttpcompatible server implementation and can be exposed wherever thehandler(request)interface is implemented, for example;As a
nodejshttpseverimport * as http from "http"; ... // create a http server instance const server = http.createServer(supagraph); // bind to port server.listen(4001, () => { console.info("Server is running on http://localhost:4001/graphql"); });
Start with:
$ node ./[filename].js
Or as a
nextjs13route.tsendpoint:import type { NextRequest, NextResponse } from "next"; ... // GET will always return the graphiql user interface... export async function GET(request: NextRequest) { try { // get html response const response = await graphql(request); // return via nextjs return new NextResponse(response.body, { status: response.status, headers: response.headers, }); } catch (e) { // eslint-disable-next-line no-console console.log(e); } } // POST methods exclusively accept and return JSON bodies... export async function POST(request: NextRequest) { try { // get json response const response = await graphql(request); // process the graphql response return new NextResponse(JSON.stringify(await response.json()), { status: response.status, headers: response.headers, }); } catch (e) { // eslint-disable-next-line no-console console.log(e); } }
Start with:
$ pnpm dev -or- pnpm build && pnpm start
Contributing
If you would like to contribute to supagraph, please follow these steps:
- Fork this repository.
- Create a new branch for your changes.
- Make your changes and test them thoroughly.
- Create a pull request and describe your changes.
Support
Support can be found on our Discord channel #supagraph