import { graphiQLHtml } from "@/ui/graphiql.html.js"; import { maxAliasesPlugin } from "@escape.tech/graphql-armor-max-aliases"; import { maxDepthPlugin } from "@escape.tech/graphql-armor-max-depth"; import { maxTokensPlugin } from "@escape.tech/graphql-armor-max-tokens"; import { type YogaServerInstance, createYoga } from "graphql-yoga"; import { createMiddleware } from "hono/factory"; import { buildGraphQLSchema } from "./buildGraphqlSchema.js"; import { buildLoaderCache } from "./buildLoaderCache.js"; /** * Middleware for GraphQL with an interactive web view. * * - Docs: https://ponder.sh/docs/query/api-functions#register-graphql-middleware * * @example * import { ponder } from "@/generated"; * import { graphql } from "@ponder/core"; * * ponder.use("/graphql", graphql()); * */ export const graphql = ( { maxOperationTokens = 1000, maxOperationDepth = 100, maxOperationAliases = 30, }: { maxOperationTokens?: number; maxOperationDepth?: number; maxOperationAliases?: number; } = { // Default limits are from Apollo: // https://www.apollographql.com/blog/prevent-graph-misuse-with-operation-size-and-complexity-limit maxOperationTokens: 1000, maxOperationDepth: 100, maxOperationAliases: 30, }, ) => { let yoga: YogaServerInstance | undefined = undefined; return createMiddleware(async (c) => { if (c.req.method === "GET") { return c.html(graphiQLHtml(c.req.path)); } if (yoga === undefined) { const readonlyStore = c.get("readonlyStore"); const metadataStore = c.get("metadataStore"); const schema = c.get("schema"); const graphqlSchema = buildGraphQLSchema(schema); yoga = createYoga({ schema: graphqlSchema, context: () => { const getLoader = buildLoaderCache({ store: readonlyStore }); return { readonlyStore, metadataStore, getLoader }; }, graphqlEndpoint: c.req.path, maskedErrors: process.env.NODE_ENV === "production", logging: false, graphiql: false, parserAndValidationCache: false, plugins: [ maxTokensPlugin({ n: maxOperationTokens }), maxDepthPlugin({ n: maxOperationDepth, ignoreIntrospection: false, }), maxAliasesPlugin({ n: maxOperationAliases, allowList: [], }), ], }); } const response = await yoga.handle(c.req.raw); // TODO: Figure out why Yoga is returning 500 status codes for GraphQL errors. // @ts-expect-error response.status = 200; // @ts-expect-error response.statusText = "OK"; return response; }); };