Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/custom-yoga-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ponder": patch
---

Added `plugins` option to `graphql()` middleware for custom Yoga/Envelop plugins (e.g., response caching, tracing). Also exported `buildGraphQLSchema` and `buildDataLoaderCache` for advanced use cases.
51 changes: 50 additions & 1 deletion packages/core/src/graphql/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import {
setupIsolatedDatabase,
} from "@/_test/setup.js";
import { onchainTable } from "@/drizzle/onchain.js";
import type { Plugin } from "graphql-yoga";
import { Hono } from "hono";
import { beforeEach, expect, test } from "vitest";
import { beforeEach, expect, test, vi } from "vitest";
import { graphql } from "./middleware.js";

beforeEach(setupCommon);
Expand Down Expand Up @@ -355,3 +356,51 @@ test("graphQLMiddleware interactive", async () => {

expect(response.status).toBe(200);
});

test("graphQLMiddleware supports custom plugins", async () => {
const schema = {
table: onchainTable("table", (t) => ({
id: t.text().primaryKey(),
})),
};

const { database } = await setupDatabaseServices({
schemaBuild: { schema },
});

globalThis.PONDER_DATABASE = database;

await database.userQB.raw.insert(schema.table).values({
id: "0",
});

// Create a simple plugin that tracks execution
const onExecuteSpy = vi.fn();
const customPlugin: Plugin = {
onExecute: onExecuteSpy,
};

const app = new Hono().use(
"/graphql",
graphql(
{ schema, db: database.readonlyQB.raw },
{ plugins: [customPlugin] },
),
);

const response = await app.request("/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: `query { table(id: "0") { id } }`,
}),
});

expect(response.status).toBe(200);
expect(await response.json()).toMatchObject({
data: { table: { id: "0" } },
});

// Verify the custom plugin was called
expect(onExecuteSpy).toHaveBeenCalled();
});
7 changes: 6 additions & 1 deletion packages/core/src/graphql/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ 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 GraphQLSchema, printSchema } from "graphql";
import { createYoga } from "graphql-yoga";
import { type Plugin, createYoga } from "graphql-yoga";
import { createMiddleware } from "hono/factory";
import { buildDataLoaderCache, buildGraphQLSchema } from "./index.js";

Expand Down Expand Up @@ -33,16 +33,20 @@ export const graphql = (
maxOperationTokens = 1000,
maxOperationDepth = 100,
maxOperationAliases = 30,
plugins: customPlugins = [],
}: {
maxOperationTokens?: number;
maxOperationDepth?: number;
maxOperationAliases?: number;
/** Custom GraphQL Yoga plugins to extend functionality (e.g., response caching, tracing) */
plugins?: Plugin[];
} = {
// Default limits are from Apollo:
// https://www.apollographql.com/blog/prevent-graph-misuse-with-operation-size-and-complexity-limits
maxOperationTokens: 1000,
maxOperationDepth: 100,
maxOperationAliases: 30,
plugins: [],
},
) => {
if (globalThis.PONDER_DATABASE === undefined) {
Expand Down Expand Up @@ -73,6 +77,7 @@ export const graphql = (
maxTokensPlugin({ n: maxOperationTokens }),
maxDepthPlugin({ n: maxOperationDepth, ignoreIntrospection: false }),
maxAliasesPlugin({ n: maxOperationAliases, allowList: [] }),
...customPlugins,
],
});

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export type { ReadonlyDrizzle } from "@/types/db.js";
export { client } from "@/client/index.js";

export { graphql } from "@/graphql/middleware.js";
export { buildGraphQLSchema, buildDataLoaderCache } from "@/graphql/index.js";

export {
sql,
Expand Down