// // Copyright 2021 Vulcanize, Inc. // import assert from 'assert'; import 'reflect-metadata'; import express, { Application } from 'express'; import { ApolloServer, PubSub } from 'apollo-server-express'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import debug from 'debug'; import 'graphql-import-node'; import { createServer } from 'http'; import { getCache } from '@vulcanize/cache'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { DEFAULT_CONFIG_PATH, getConfig, getCustomProvider, JobQueue, KIND_ACTIVE } from '@vulcanize/util'; import typeDefs from './schema'; import { createResolvers as createMockResolvers } from './mock/resolvers'; import { createResolvers } from './resolvers'; import { Indexer } from './indexer'; import { Database } from './database'; import { EventWatcher } from './events'; const log = debug('vulcanize:server'); export const main = async (): Promise => { const argv = await yargs(hideBin(process.argv)) .option('f', { alias: 'config-file', demandOption: true, describe: 'configuration file path (toml)', type: 'string', default: DEFAULT_CONFIG_PATH }) .argv; const config = await getConfig(argv.f); assert(config.server, 'Missing server config'); const { host, port, mode, kind: watcherKind } = config.server; const { upstream, database: dbConfig, jobQueue: jobQueueConfig } = config; assert(dbConfig, 'Missing database config'); const db = new Database(dbConfig); await db.init(); assert(upstream, 'Missing upstream config'); const { ethServer: { gqlApiEndpoint, gqlPostgraphileEndpoint, rpcProviderEndpoint }, cache: cacheConfig } = upstream; assert(gqlApiEndpoint, 'Missing upstream ethServer.gqlApiEndpoint'); assert(gqlPostgraphileEndpoint, 'Missing upstream ethServer.gqlPostgraphileEndpoint'); const cache = await getCache(cacheConfig); const ethClient = new EthClient({ gqlEndpoint: gqlApiEndpoint, gqlSubscriptionEndpoint: gqlPostgraphileEndpoint, cache }); const postgraphileClient = new EthClient({ gqlEndpoint: gqlPostgraphileEndpoint, cache }); const ethProvider = getCustomProvider(rpcProviderEndpoint); // Note: In-memory pubsub works fine for now, as each watcher is a single process anyway. // Later: https://www.apollographql.com/docs/apollo-server/data/subscriptions/#production-pubsub-libraries const pubsub = new PubSub(); assert(jobQueueConfig, 'Missing job queue config'); const { dbConnectionString, maxCompletionLagInSecs } = jobQueueConfig; assert(dbConnectionString, 'Missing job queue db connection string'); const jobQueue = new JobQueue({ dbConnectionString, maxCompletionLag: maxCompletionLagInSecs }); const indexer = new Indexer(db, ethClient, postgraphileClient, ethProvider, jobQueue, mode); const eventWatcher = new EventWatcher(upstream, ethClient, postgraphileClient, indexer, pubsub, jobQueue); if (watcherKind === KIND_ACTIVE) { await jobQueue.start(); await eventWatcher.start(); } const resolvers = process.env.MOCK ? await createMockResolvers() : await createResolvers(indexer, eventWatcher); const app: Application = express(); const server = new ApolloServer({ typeDefs, resolvers }); await server.start(); server.applyMiddleware({ app }); const httpServer = createServer(app); server.installSubscriptionHandlers(httpServer); httpServer.listen(port, host, () => { log(`Server is listening on host ${host} port ${port}`); }); return { app, server }; }; main().then(() => { log('Starting server...'); }).catch(err => { log(err); });