Untitled

mail@pastecode.io avatar
unknown
plain_text
10 days ago
5.9 kB
7
Indexable
Never
import Website from "@src/structure/Website.js";
import { LogType } from "@src/utils/logger.js";
import { readdir } from "fs/promises";
import p from "path";
import { Request, Response, NextFunction } from "express";
import { fileURLToPath, pathToFileURL } from "url";
import Server from "./Server.js";
import { buildSubgraphSchema } from "@apollo/subgraph";
import { GraphQLSchema } from "graphql";
import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway";
import { useServer } from "graphql-ws/lib/use/ws";
import { ApolloServer } from "@apollo/server";
import { expressMiddleware } from "@apollo/server/express4";
import { ApolloServerPluginDrainHttpServer } from "@apollo/server/plugin/drainHttpServer";
import { execute, subscribe } from "graphql";
import { exec } from "child_process";
import fs from "fs/promises";
import { printSchema } from 'graphql';
import { getStitchedSchemaFromSupergraphSdl } from '@graphql-tools/federation'
interface Params {
  server: Server;
  website: Website;
}

interface Context {
  req?: Request;
  server: Server;
  website: Website;
}
interface WsContext {
  req?: Request;
  server: Server;
  website: Website;
}
class GraphQL {
  server: Server;
  website: Website;
  subgraphs: Map<string, GraphQLSchema>;
  services: { name: string; url: string, schemaSDLPath: string }[];
  dir: string;

  constructor(website: Website) {
    this.website = website;
    this.server = this.website.server;
    this.subgraphs = new Map();
    this.services = [];
    this.dir = p.join(p.dirname(fileURLToPath(import.meta.url)), "..",
      "graphql",)
  }

  async context(...d: any): Promise<Context> {
    return { server: this.server, website: this.website, ...d };
  }
  async wsContext(...d: any): Promise<WsContext> {
    console.log(d);
    return { server: this.server, website: this.website, ...d };
  }
  async loadSubGraphs() {
    const subgraphDirectory = p.join(
      this.dir,
      "subgraphs"
    );
    const subgraphNames = await readdir(subgraphDirectory);
    for (const subgraphName of subgraphNames) {
      const schemaModule = await import(
        pathToFileURL(p.join(subgraphDirectory, subgraphName, "schema.js"))
          .href
      ).catch(() => null);

      const params: Params = {
        server: this.server,
        website: this.website,
      };
      const schema =
        schemaModule && schemaModule.default && schemaModule.default(params);

      if (schema && schema instanceof GraphQLSchema) {

        this.subgraphs.set(subgraphName, schema);
        this.website.logger.log(
          LogType.Info,
          `Loaded subgraph ${subgraphName}.`
        );
      } else {
        this.website.logger.log(
          LogType.Error,
          `Failed to load subgraph ${subgraphName}.`
        );
      }
    }
  }

  async startSubgraphs() {
    const subgraphDirectory = p.join(
      this.dir,
      "subgraphs"
    );
    for (const [subgraphName, subgraph] of this.subgraphs.entries()) {
      const schema = subgraph;
      const path = `/${subgraphName}/graphql`;
      const httpServer = this.server.server;
      // Initialize Apollo Server for HTTP requests
      const server = new ApolloServer<Context>({
        schema,
        csrfPrevention: false,
        plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
      });

      await server.start();
      const wsServer = this.server.wsServer.createNamespace(path);
      useServer(
        {
          schema,
          execute,
          subscribe,
          context: this.wsContext.bind(this),
        },
        wsServer
      );
      this.server.express.use(
        path,
        expressMiddleware(server, {
          context: this.context.bind(this),
        })
      );
      const schemaSDLPath = p.join(subgraphDirectory, subgraphName, "schema.graphql");
      await fs.writeFile(
        schemaSDLPath,
        printSchema(schema)
      );
      this.services.push({
        name: subgraphName,
        url: this.server.website.config.apiDomain + path,
        schemaSDLPath,
      });
      this.website.logger.log(
        LogType.Info,
        `Initialized service ${subgraphName} on ${path} route.`
      );
      this.website.logger.log(
        LogType.Info,
        `Initialized service websocket ${subgraphName} on ${path} route.`
      );
    }
  }

  async loadGateway() {
    const subgraphs = Array.from(this.subgraphs.values());
    const services = this.services;
    const httpServer = this.server.server;

    const gateway = new ApolloGateway({
      supergraphSdl: new IntrospectAndCompose({
        subgraphs: services.map((service) => ({
          name: service.name,
          url: service.url,
        })),
      }),
    });
    const path = `/graphql`;
    const server = new ApolloServer<Context>({
      gateway,
      plugins: [ApolloServerPluginDrainHttpServer({ httpServer })],
    });

    await server.start();
 
  
    this.server.express.use(
      path,
      expressMiddleware(server, {
        context: this.context.bind(this),
      })
    );

    this.website.logger.log(
      LogType.Info,
      `Loaded gateway with with ${subgraphs.length} subgraphs on ${path} route.`
    );
    this.website.logger.log(
      LogType.Info,
      `Loaded websocket gateway with ${subgraphs.length} subgraphs on ${path} route.`
    );
  }
  public async start(): Promise<void> {
    this.website.logger.log(LogType.Info, `Starting GraphQL...`);

    await this.loadSubGraphs();
    await this.startSubgraphs();
    await this.loadGateway();
    // await this.loadGateway();


    this.website.logger.log(
      LogType.Ready,
      `GraphQL is ready with Apollo Router.`
    );
  }
}

export default GraphQL;
export { Params, Context };
Leave a Comment