Repository: ponder-sh/ponder
Pull request #1257 (2024-11-19 17:10:24)

fix: `schema.graphql` codegen
Before, `buildGraphqlSchema` needed the built `drizzle` object to access the relational config, which was created by `createDatabase`. Now, it only needs the raw `schema` object and builds the relational config manually using helper functions from Drizzle. This decouples the GraphQL schema build from the database build. With that done, it was easy to move the GraphQL schema build from the server/middleware back to the build step.


Patch:
diff --git a/.changeset/proud-pigs-sniff.md b/.changeset/proud-pigs-sniff.md
new file mode 100644
index 00000000..f152585c
--- /dev/null
+++ b/.changeset/proud-pigs-sniff.md
@@ -0,0 +1,5 @@
+---
+"@ponder/core": patch
+---
+
+Fixed a bug where the `codegen` command did not generate `generated/schema.graphql`.
diff --git a/packages/core/src/bin/commands/codegen.ts b/packages/core/src/bin/commands/codegen.ts
index 9d79892c..66b0e080 100644
--- a/packages/core/src/bin/commands/codegen.ts
+++ b/packages/core/src/bin/commands/codegen.ts
@@ -57,7 +57,8 @@ export async function codegen({ cliOptions }: { cliOptions: CliOptions }) {
     properties: { cli_command: "codegen" },
   });
 
-  runCodegen({ common });
+  const graphqlSchema = buildResult.indexingBuild.graphqlSchema;
+  runCodegen({ common, graphqlSchema });
 
   logger.info({ service: "codegen", msg: "Wrote ponder-env.d.ts" });
   logger.info({ service: "codegen", msg: "Wrote schema.graphql" });
diff --git a/packages/core/src/bin/commands/serve.ts b/packages/core/src/bin/commands/serve.ts
index 4e953451..a088c123 100644
--- a/packages/core/src/bin/commands/serve.ts
+++ b/packages/core/src/bin/commands/serve.ts
@@ -89,10 +89,10 @@ export async function serve({ cliOptions }: { cliOptions: CliOptions }) {
   });
 
   const server = await createServer({
+    common,
     app: buildResult.apiBuild.app,
     routes: buildResult.apiBuild.routes,
-    common,
-    schema,
+    graphqlSchema: buildResult.indexingBuild.graphqlSchema,
     database,
     instanceId:
       process.env.PONDER_EXPERIMENTAL_INSTANCE_ID === undefined
diff --git a/packages/core/src/bin/utils/run.ts b/packages/core/src/bin/utils/run.ts
index 48e3945f..ecae740a 100644
--- a/packages/core/src/bin/utils/run.ts
+++ b/packages/core/src/bin/utils/run.ts
@@ -32,7 +32,14 @@ export async function run({
   onFatalError: (error: Error) => void;
   onReloadableError: (error: Error) => void;
 }) {
-  const { instanceId, networks, sources, schema, indexingFunctions } = build;
+  const {
+    instanceId,
+    networks,
+    sources,
+    schema,
+    indexingFunctions,
+    graphqlSchema,
+  } = build;
 
   let isKilled = false;
 
@@ -52,7 +59,7 @@ export async function run({
   // starting the server so the app can become responsive more quickly.
   await database.migrateSync();
 
-  runCodegen({ common });
+  runCodegen({ common, graphqlSchema });
 
   // Note: can throw
   const sync = await createSync({
diff --git a/packages/core/src/bin/utils/runServer.ts b/packages/core/src/bin/utils/runServer.ts
index 7cc5f517..9e0eb016 100644
--- a/packages/core/src/bin/utils/runServer.ts
+++ b/packages/core/src/bin/utils/runServer.ts
@@ -15,13 +15,13 @@ export async function runServer({
   build: ApiBuild;
   database: Database;
 }) {
-  const { schema, instanceId } = build;
+  const { instanceId, graphqlSchema } = build;
 
   const server = await createServer({
     app: build.app,
     routes: build.routes,
     common,
-    schema,
+    graphqlSchema,
     database,
     instanceId,
   });
diff --git a/packages/core/src/build/schema.ts b/packages/core/src/build/schema.ts
index 838475ab..6d2e431f 100644
--- a/packages/core/src/build/schema.ts
+++ b/packages/core/src/build/schema.ts
@@ -1,6 +1,7 @@
 import { BuildError } from "@/common/errors.js";
 import { type Schema, isPgEnumSym } from "@/drizzle/index.js";
 import { getSql } from "@/drizzle/kit/index.js";
+import { buildGraphQLSchema } from "@/graphql/index.js";
 import { SQL, getTableColumns, is } from "drizzle-orm";
 import {
   PgBigSerial53,
@@ -192,10 +193,12 @@ export const safeBuildSchema = ({
 }: { schema: Schema; instanceId: string }) => {
   try {
     const result = buildSchema({ schema, instanceId });
+    const graphqlSchema = buildGraphQLSchema(schema);
 
     return {
       status: "success",
       ...result,
+      graphqlSchema,
     } as const;
   } catch (_error) {
     const buildError = new BuildError((_error as Error).message);
diff --git a/packages/core/src/build/service.ts b/packages/core/src/build/service.ts
index 933d4975..28f41155 100644
--- a/packages/core/src/build/service.ts
+++ b/packages/core/src/build/service.ts
@@ -13,6 +13,7 @@ import type { PonderRoutes } from "@/hono/index.js";
 import type { Source } from "@/sync/source.js";
 import { serialize } from "@/utils/serialize.js";
 import { glob } from "glob";
+import type { GraphQLSchema } from "graphql";
 import type { Hono } from "hono";
 import { type ViteDevServer, createServer } from "vite";
 import { ViteNodeRunner } from "vite-node/client";
@@ -57,6 +58,7 @@ type BaseBuild = {
   schema: Schema;
   statements: SqlStatements;
   namespace: string;
+  graphqlSchema: GraphQLSchema;
 };
 
 export type IndexingBuild = BaseBuild & {
@@ -748,6 +750,7 @@ const validateAndBuild = async (
       schema: schema.schema,
       statements: buildSchemaResult.statements,
       namespace: buildSchemaResult.namespace,
+      graphqlSchema: buildSchemaResult.graphqlSchema,
     },
   };
 };
diff --git a/packages/core/src/common/codegen.ts b/packages/core/src/common/codegen.ts
index fa4857c6..0090ab92 100644
--- a/packages/core/src/common/codegen.ts
+++ b/packages/core/src/common/codegen.ts
@@ -1,6 +1,7 @@
-import { writeFileSync } from "node:fs";
+import { mkdirSync, writeFileSync } from "node:fs";
 import path from "node:path";
 import type { Common } from "@/common/common.js";
+import { type GraphQLSchema, printSchema } from "graphql";
 
 export const ponderEnv = `// This file enables type checking and editor autocomplete for this Ponder project.
 // After upgrading, you may find that changes have been made to this file.
@@ -31,7 +32,10 @@ declare module "@/generated" {
 }
 `;
 
-export function runCodegen({ common }: { common: Common }) {
+export function runCodegen({
+  common,
+  graphqlSchema,
+}: { common: Common; graphqlSchema: GraphQLSchema }) {
   writeFileSync(
     path.join(common.options.rootDir, "ponder-env.d.ts"),
     ponderEnv,
@@ -42,4 +46,16 @@ export function runCodegen({ common }: { common: Common }) {
     service: "codegen",
     msg: "Wrote new file at ponder-env.d.ts",
   });
+
+  mkdirSync(common.options.generatedDir, { recursive: true });
+  writeFileSync(
+    path.join(common.options.generatedDir, "schema.graphql"),
+    printSchema(graphqlSchema),
+    "utf-8",
+  );
+
+  common.logger.debug({
+    service: "codegen",
+    msg: "Wrote new file at generated/schema.graphql",
+  });
 }
diff --git a/packages/core/src/graphql/index.ts b/packages/core/src/graphql/index.ts
index ba8edd4d..ab43152d 100644
--- a/packages/core/src/graphql/index.ts
+++ b/packages/core/src/graphql/index.ts
@@ -12,8 +12,10 @@ import {
   arrayContained,
   arrayContains,
   asc,
+  createTableRelationsHelpers,
   desc,
   eq,
+  extractTablesRelationalConfig,
   getTableColumns,
   gt,
   gte,
@@ -59,6 +61,7 @@ type Parent = Record<string, any>;
 type Context = {
   getDataLoader: ReturnType<typeof buildDataLoaderCache>;
   metadataStore: MetadataStore;
+  drizzle: Drizzle<{ [key: string]: OnchainTable }>;
 };
 
 type PluralArgs = {
@@ -73,10 +76,15 @@ type PluralArgs = {
 const DEFAULT_LIMIT = 50 as const;
 const MAX_LIMIT = 1000 as const;
 
-export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
-  const tables = Object.values(db._.schema ?? {}) as TableRelationalConfig[];
+export function buildGraphQLSchema(schema: Schema): GraphQLSchema {
+  const tablesConfig = extractTablesRelationalConfig(
+    schema,
+    createTableRelationsHelpers,
+  );
+
+  const tables = Object.values(tablesConfig.tables) as TableRelationalConfig[];
 
-  const enums = Object.entries(db._.fullSchema ?? {}).filter(
+  const enums = Object.entries(schema).filter(
     (el): el is [string, PgEnum<[string, ...string[]]>] => isPgEnum(el[1]),
   );
   const enumTypes: Record<string, GraphQLEnumType> = {};
@@ -208,13 +216,6 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
               `Internal error: Referenced entity type not found for table "${referencedTable.tsName}" `,
             );
 
-          const baseQuery = (db as Drizzle<{ [key: string]: OnchainTable }>)
-            .query[referencedTable.tsName];
-          if (!baseQuery)
-            throw new Error(
-              `Internal error: Referenced table "${referencedTable.tsName}" not found in RQB`,
-            );
-
           if (is(relation, One)) {
             const fields = relation.config?.fields ?? [];
             const references = relation.config?.references ?? [];
@@ -279,7 +280,7 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
                 after: { type: GraphQLString },
                 limit: { type: GraphQLInt },
               },
-              resolve: async (parent, args: PluralArgs, _context) => {
+              resolve: async (parent, args: PluralArgs, context) => {
                 const relationalConditions = [];
                 for (let i = 0; i < references.length; i++) {
                   const column = fields[i]!;
@@ -287,6 +288,12 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
                   relationalConditions.push(eq(column, value));
                 }
 
+                const baseQuery = context.drizzle.query[referencedTable.tsName];
+                if (!baseQuery)
+                  throw new Error(
+                    `Internal error: Referenced table "${referencedTable.tsName}" not found in RQB`,
+                  );
+
                 return executePluralQuery(
                   table,
                   baseQuery,
@@ -329,14 +336,6 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
       table.tsName.charAt(0).toLowerCase() + table.tsName.slice(1);
     const pluralFieldName = `${singularFieldName}s`;
 
-    const baseQuery = (db as Drizzle<{ [key: string]: OnchainTable }>).query[
-      table.tsName
-    ];
-    if (!baseQuery)
-      throw new Error(
-        `Internal error: Table "${table.tsName}" not found in RQB`,
-      );
-
     queryFields[singularFieldName] = {
       type: entityType,
       // Find the primary key columns and GraphQL core types and include them
@@ -351,7 +350,13 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
           },
         ]),
       ),
-      resolve: async (_parent, args, _context) => {
+      resolve: async (_parent, args, context) => {
+        const baseQuery = context.drizzle.query[table.tsName];
+        if (!baseQuery)
+          throw new Error(
+            `Internal error: Table "${table.tsName}" not found in RQB`,
+          );
+
         // The `args` object here should be a valid `where` argument that
         // uses the `eq` shorthand for each primary key column.
         const whereConditions = buildWhereConditions(args, table.columns);
@@ -373,7 +378,13 @@ export function buildGraphQLSchema(db: Drizzle<Schema>): GraphQLSchema {
         after: { type: GraphQLString },
         limit: { type: GraphQLInt },
       },
-      resolve: async (_parent, args: PluralArgs, _context) => {
+      resolve: async (_parent, args: PluralArgs, context) => {
+        const baseQuery = context.drizzle.query[table.tsName];
+        if (!baseQuery)
+          throw new Error(
+            `Internal error: Table "${table.tsName}" not found in RQB`,
+          );
+
         return executePluralQuery(table, baseQuery, args);
       },
     };
diff --git a/packages/core/src/graphql/middleware.ts b/packages/core/src/graphql/middleware.ts
index 3f1b5d0b..fe108984 100644
--- a/packages/core/src/graphql/middleware.ts
+++ b/packages/core/src/graphql/middleware.ts
@@ -1,13 +1,10 @@
-import { mkdirSync, writeFileSync } from "node:fs";
-import path from "node:path";
 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 { printSchema } from "graphql";
 import { type YogaServerInstance, createYoga } from "graphql-yoga";
 import { createMiddleware } from "hono/factory";
-import { buildDataLoaderCache, buildGraphQLSchema } from "./index.js";
+import { buildDataLoaderCache } from "./index.js";
 
 /**
  * Middleware for GraphQL with an interactive web view.
@@ -47,29 +44,14 @@ export const graphql = (
 
     if (yoga === undefined) {
       const metadataStore = c.get("metadataStore");
-      const common = c.get("common");
+      const graphqlSchema = c.get("graphqlSchema");
       const drizzle = c.get("db");
 
-      const graphqlSchema = buildGraphQLSchema(drizzle);
-
-      // Write schema.graphql once on startup
-      mkdirSync(common.options.generatedDir, { recursive: true });
-      writeFileSync(
-        path.join(common.options.generatedDir, "schema.graphql"),
-        printSchema(graphqlSchema),
-        "utf-8",
-      );
-
-      common.logger.debug({
-        service: "codegen",
-        msg: "Wrote new file at generated/schema.graphql",
-      });
-
       yoga = createYoga({
         schema: graphqlSchema,
         context: () => {
           const getDataLoader = buildDataLoaderCache({ drizzle });
-          return { metadataStore, getDataLoader };
+          return { drizzle, metadataStore, getDataLoader };
         },
         graphqlEndpoint: c.req.path,
         maskedErrors: process.env.NODE_ENV === "production",
diff --git a/packages/core/src/server/index.ts b/packages/core/src/server/index.ts
index dcffffbb..8b0d7e6e 100644
--- a/packages/core/src/server/index.ts
+++ b/packages/core/src/server/index.ts
@@ -1,7 +1,6 @@
 import http from "node:http";
 import type { Common } from "@/common/common.js";
 import type { Database } from "@/database/index.js";
-import type { Schema } from "@/drizzle/index.js";
 import { graphql } from "@/graphql/middleware.js";
 import { type PonderRoutes, applyHonoRoutes } from "@/hono/index.js";
 import {
@@ -10,6 +9,7 @@ import {
 } from "@/indexing-store/metadata.js";
 import { startClock } from "@/utils/timer.js";
 import { serve } from "@hono/node-server";
+import type { GraphQLSchema } from "graphql";
 import { Hono } from "hono";
 import { cors } from "hono/cors";
 import { createMiddleware } from "hono/factory";
@@ -26,14 +26,14 @@ export async function createServer({
   app: userApp,
   routes: userRoutes,
   common,
-  schema,
+  graphqlSchema,
   database,
   instanceId,
 }: {
   app: Hono;
   routes: PonderRoutes;
   common: Common;
-  schema: Schema;
+  graphqlSchema: GraphQLSchema;
   database: Database;
   instanceId?: string;
 }): Promise<Server> {
@@ -93,10 +93,9 @@ export async function createServer({
 
   // context required for graphql middleware and hono middleware
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", common);
     c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
-    c.set("schema", schema);
+    c.set("graphqlSchema", graphqlSchema);
     await next();
   });
 


Test patch:
diff --git a/packages/core/src/bin/utils/run.test.ts b/packages/core/src/bin/utils/run.test.ts
index 6e437f97..a36b532f 100644
--- a/packages/core/src/bin/utils/run.test.ts
+++ b/packages/core/src/bin/utils/run.test.ts
@@ -7,6 +7,7 @@ import type { IndexingBuild } from "@/build/index.js";
 import { buildSchema } from "@/build/schema.js";
 import { createDatabase } from "@/database/index.js";
 import { onchainTable } from "@/drizzle/index.js";
+import { buildGraphQLSchema } from "@/graphql/index.js";
 import { promiseWithResolvers } from "@ponder/common";
 import { beforeEach, expect, test, vi } from "vitest";
 import { run } from "./run.js";
@@ -20,7 +21,8 @@ const account = onchainTable("account", (p) => ({
   balance: p.bigint().notNull(),
 }));
 
-// const graphqlSchema = buildGraphQLSchema({ schema: { account } });
+const schema = { account };
+const graphqlSchema = buildGraphQLSchema(schema);
 
 test("run() setup", async (context) => {
   const indexingFunctions = {
@@ -28,14 +30,15 @@ test("run() setup", async (context) => {
   };
 
   const { statements, namespace } = buildSchema({
-    schema: { account },
+    schema,
     instanceId: "1234",
   });
 
   const build: IndexingBuild = {
     buildId: "buildId",
     instanceId: "1234",
-    schema: { account },
+    schema,
+    graphqlSchema,
     databaseConfig: context.databaseConfig,
     networks: context.networks,
     sources: context.sources,
@@ -46,7 +49,7 @@ test("run() setup", async (context) => {
 
   const database = createDatabase({
     common: context.common,
-    schema: { account },
+    schema,
     databaseConfig: context.databaseConfig,
     instanceId: "1234",
     buildId: "buildId",
@@ -76,14 +79,15 @@ test("run() setup error", async (context) => {
   const onReloadableErrorPromiseResolver = promiseWithResolvers<void>();
 
   const { statements, namespace } = buildSchema({
-    schema: { account },
+    schema,
     instanceId: "1234",
   });
 
   const build: IndexingBuild = {
     buildId: "buildId",
     instanceId: "1234",
-    schema: { account },
+    schema,
+    graphqlSchema,
     databaseConfig: context.databaseConfig,
     networks: context.networks,
     sources: context.sources,
@@ -94,7 +98,7 @@ test("run() setup error", async (context) => {
 
   const database = createDatabase({
     common: context.common,
-    schema: { account },
+    schema,
     databaseConfig: context.databaseConfig,
     instanceId: "1234",
     buildId: "buildId",
diff --git a/packages/core/src/graphql/index.test.ts b/packages/core/src/graphql/index.test.ts
index 9c221e88..6da6be61 100644
--- a/packages/core/src/graphql/index.test.ts
+++ b/packages/core/src/graphql/index.test.ts
@@ -3,7 +3,9 @@ import {
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
+import type { Database } from "@/database/index.js";
 import { onchainEnum, onchainTable, primaryKey } from "@/drizzle/index.js";
+import type { MetadataStore } from "@/indexing-store/metadata.js";
 import { relations } from "drizzle-orm";
 import { type GraphQLType, execute, parse } from "graphql";
 import { beforeEach, expect, test, vi } from "vitest";
@@ -12,6 +14,12 @@ import { buildDataLoaderCache, buildGraphQLSchema } from "./index.js";
 beforeEach(setupCommon);
 beforeEach(setupIsolatedDatabase);
 
+function buildContextValue(database: Database, metadataStore: MetadataStore) {
+  const drizzle = database.drizzle;
+  const getDataLoader = buildDataLoaderCache({ drizzle });
+  return { drizzle, metadataStore, getDataLoader };
+}
+
 test("metadata", async (context) => {
   const schema = {};
 
@@ -19,12 +27,11 @@ test("metadata", async (context) => {
     context,
     { schema },
   );
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   await metadataStore.setStatus({
     mainnet: {
@@ -99,8 +106,7 @@ test("scalar, scalar not null, scalar array, scalar array not null", async (cont
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -135,7 +141,7 @@ test("scalar, scalar not null, scalar array, scalar array not null", async (cont
     bigintArrayNotNull: [0n],
   });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -224,8 +230,7 @@ test("enum, enum not null, enum array, enum array not null", async (context) =>
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -237,7 +242,7 @@ test("enum, enum not null, enum array, enum array not null", async (context) =>
     enumArrayNotNull: ["A"],
   });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -276,8 +281,7 @@ test("json, json not null", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -287,7 +291,7 @@ test("json, json not null", async (context) => {
     jsonNotNull: { kevin: 52 },
   });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -332,8 +336,7 @@ test("singular", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -347,7 +350,7 @@ test("singular", async (context) => {
     { owner: "1", spender: "0", amount: 100n },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -430,8 +433,7 @@ test("singular with one relation", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -443,7 +445,7 @@ test("singular with one relation", async (context) => {
     .insert(schema.pet)
     .values({ id: "dog1", ownerIdNotNull: "jake" });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -496,8 +498,7 @@ test("singular with many relation", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -510,7 +511,7 @@ test("singular with many relation", async (context) => {
     { id: "dog3", ownerId: "kyle" },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -554,8 +555,7 @@ test("singular with many relation using filter", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -568,7 +568,7 @@ test("singular with many relation using filter", async (context) => {
     { id: "dog3", age: 3, ownerId: "jake" },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -617,8 +617,7 @@ test("plural with one relation uses dataloader", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -631,7 +630,7 @@ test("plural with one relation uses dataloader", async (context) => {
     { id: "dog3", ownerId: "kyle" },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   // @ts-expect-error
   const personFindManySpy = vi.spyOn(database.drizzle.query.person, "findMany");
@@ -689,11 +688,9 @@ test("filter input type", async (context) => {
   }));
   const schema = { simpleEnum, table };
 
-  const { database, cleanup } = await setupDatabaseServices(context, {
-    schema,
-  });
+  const { cleanup } = await setupDatabaseServices(context, { schema });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
   const typeMap = graphqlSchema.getTypeMap();
   const tableFilterType = typeMap.tableFilter!;
   const fields = (tableFilterType.toConfig() as any).fields as Record<
@@ -823,8 +820,7 @@ test("filter universal", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -832,7 +828,7 @@ test("filter universal", async (context) => {
     .insert(schema.person)
     .values([{ id: 1n }, { id: 2n }, { id: 3n }]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -873,8 +869,7 @@ test("filter singular", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -882,7 +877,7 @@ test("filter singular", async (context) => {
     .insert(schema.person)
     .values([{ id: "0x01" }, { id: "0x02" }, { id: "0x03" }]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -926,8 +921,7 @@ test("filter plural", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -937,7 +931,7 @@ test("filter plural", async (context) => {
     { id: "3", number: [5, 6, 7] },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1035,8 +1029,7 @@ test("filter numeric", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1067,7 +1060,7 @@ test("filter numeric", async (context) => {
     },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1160,8 +1153,7 @@ test("filter string", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1171,7 +1163,7 @@ test("filter string", async (context) => {
     { id: "3", text: "three", hex: "0xef0" },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1230,8 +1222,7 @@ test("filter and/or", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1243,7 +1234,7 @@ test("filter and/or", async (context) => {
     { id: "id5", name: "Winston", age: 12 },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -1281,8 +1272,7 @@ test("order by", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1313,7 +1303,7 @@ test("order by", async (context) => {
     },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1401,8 +1391,7 @@ test("limit", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1410,7 +1399,7 @@ test("limit", async (context) => {
     await indexingStore.insert(schema.person).values({ id: String(i) });
   }
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   // Default limit of 50
   let result = await query(`
@@ -1468,8 +1457,7 @@ test("cursor pagination ascending", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1485,7 +1473,7 @@ test("cursor pagination ascending", async (context) => {
     { id: "id9", name: "Last" },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1611,8 +1599,7 @@ test("cursor pagination descending", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1624,7 +1611,7 @@ test("cursor pagination descending", async (context) => {
     { id: "id5", name: "Winston", age: 12 },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1743,8 +1730,7 @@ test("cursor pagination start and end cursors", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1756,7 +1742,7 @@ test("cursor pagination start and end cursors", async (context) => {
     { id: "id5", name: "Winston", age: 12 },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
@@ -1809,8 +1795,7 @@ test("cursor pagination has previous page", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1822,7 +1807,7 @@ test("cursor pagination has previous page", async (context) => {
     { id: "id5", name: "Winston", age: 12 },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -1895,8 +1880,7 @@ test("cursor pagination composite primary key", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -1909,7 +1893,7 @@ test("cursor pagination composite primary key", async (context) => {
     { owner: "jenny", spender: "bill", amount: 800n },
   ]);
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   let result = await query(`
     query {
@@ -2035,8 +2019,7 @@ test("column casing", async (context) => {
 
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
-  const getDataLoader = buildDataLoaderCache({ drizzle: database.drizzle });
-  const contextValue = { metadataStore, getDataLoader };
+  const contextValue = buildContextValue(database, metadataStore);
   const query = (source: string) =>
     execute({ schema: graphqlSchema, contextValue, document: parse(source) });
 
@@ -2046,7 +2029,7 @@ test("column casing", async (context) => {
     camelCase: "0",
   });
 
-  const graphqlSchema = buildGraphQLSchema(database.drizzle);
+  const graphqlSchema = buildGraphQLSchema(schema);
 
   const result = await query(`
     query {
diff --git a/packages/core/src/graphql/middleware.test.ts b/packages/core/src/graphql/middleware.test.ts
index 1fb2b545..b6444a3b 100644
--- a/packages/core/src/graphql/middleware.test.ts
+++ b/packages/core/src/graphql/middleware.test.ts
@@ -7,6 +7,7 @@ import { onchainTable } from "@/drizzle/index.js";
 import { Hono } from "hono";
 import { createMiddleware } from "hono/factory";
 import { beforeEach, expect, test } from "vitest";
+import { buildGraphQLSchema } from "./index.js";
 import { graphql } from "./middleware.js";
 
 beforeEach(setupCommon);
@@ -25,13 +26,15 @@ test("middleware serves request", async (context) => {
     })),
   };
 
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, indexingStore, metadataStore, cleanup } =
     await setupDatabaseServices(context, { schema });
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
@@ -101,15 +104,17 @@ test("middleware throws error when extra filter is applied", async (context) =>
     })),
   };
 
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, metadataStore, cleanup } = await setupDatabaseServices(
     context,
     { schema },
   );
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
@@ -151,15 +156,17 @@ test("graphQLMiddleware throws error for token limit", async (context) => {
     })),
   };
 
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, metadataStore, cleanup } = await setupDatabaseServices(
     context,
     { schema },
   );
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
@@ -207,15 +214,17 @@ test("graphQLMiddleware throws error for depth limit", async (context) => {
     })),
   };
 
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, metadataStore, cleanup } = await setupDatabaseServices(
     context,
     { schema },
   );
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
@@ -263,15 +272,17 @@ test("graphQLMiddleware throws error for max aliases", async (context) => {
     })),
   };
 
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, metadataStore, cleanup } = await setupDatabaseServices(
     context,
     { schema },
   );
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
@@ -323,15 +334,18 @@ test("graphQLMiddleware throws error for max aliases", async (context) => {
 });
 
 test("graphQLMiddleware interactive", async (context) => {
+  const schema = {};
+  const graphqlSchema = buildGraphQLSchema(schema);
+
   const { database, metadataStore, cleanup } = await setupDatabaseServices(
     context,
-    { schema: {} },
+    { schema },
   );
 
   const contextMiddleware = createMiddleware(async (c, next) => {
-    c.set("common", context.common);
-    c.set("db", database.drizzle);
     c.set("metadataStore", metadataStore);
+    c.set("graphqlSchema", graphqlSchema);
+    c.set("db", database.drizzle);
     await next();
   });
 
diff --git a/packages/core/src/server/index.test.ts b/packages/core/src/server/index.test.ts
index dca312ac..2a1350be 100644
--- a/packages/core/src/server/index.test.ts
+++ b/packages/core/src/server/index.test.ts
@@ -3,6 +3,7 @@ import {
   setupDatabaseServices,
   setupIsolatedDatabase,
 } from "@/_test/setup.js";
+import { buildGraphQLSchema } from "@/graphql/index.js";
 import type { Context } from "@/hono/context.js";
 import { getMetadataStore } from "@/indexing-store/metadata.js";
 import { Hono } from "hono";
@@ -19,7 +20,7 @@ test("port", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -27,7 +28,7 @@ test("port", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -45,7 +46,7 @@ test("listens on ipv4", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -63,7 +64,7 @@ test("listens on ipv6", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -81,7 +82,7 @@ test("not ready", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -100,7 +101,7 @@ test("ready", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -124,7 +125,7 @@ test("health", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -143,7 +144,7 @@ test("healthy PUT", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -164,7 +165,7 @@ test("metrics", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -183,7 +184,7 @@ test("metrics error", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -205,7 +206,7 @@ test("metrics PUT", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -226,7 +227,7 @@ test("metrics unmatched route", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -251,7 +252,7 @@ test("missing route", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -272,7 +273,7 @@ test("custom api route", async (context) => {
     routes: [
       { method: "GET", pathOrHandlers: ["/hi", (c: Context) => c.text("hi")] },
     ],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -294,7 +295,7 @@ test("custom hono route", async (context) => {
     common: context.common,
     app,
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });
 
@@ -316,7 +317,7 @@ test.skip("kill", async (context) => {
     common: context.common,
     app: new Hono(),
     routes: [],
-    schema: {},
+    graphqlSchema: buildGraphQLSchema({}),
     database,
   });