Skip to content

Instantly share code, notes, and snippets.

@litewarp
Last active February 10, 2026 13:50
Show Gist options
  • Select an option

  • Save litewarp/886159c83a43f8f22d3baeba38e1da23 to your computer and use it in GitHub Desktop.

Select an option

Save litewarp/886159c83a43f8f22d3baeba38e1da23 to your computer and use it in GitHub Desktop.
Graphile Hono Middleware Adapter
import { transformToRelayResponse } from './transformer.js';
import type { HonoEnv } from './types.js';
// oxlint-disable-next-line unicorn/require-module-specifiers
import type {} from '@grafserv/persisted';
import type { StreamingApi } from 'hono/utils/stream';
import { mapJwtClaimsToPgSettings } from '@repo/auth';
import { getPreset } from '@repo/graphile-preset';
import { schema } from '@repo/graphile-schema';
import { grafast } from 'grafast';
import { resolvePreset } from 'graphile-config';
import { createMiddleware } from 'hono/factory';
import { HTTPException } from 'hono/http-exception';
import { stream } from 'hono/streaming';
import * as z from 'zod/mini';
export function isAsyncIterable(
maybeAsyncIterable: any
): maybeAsyncIterable is AsyncIterable<unknown> {
return typeof maybeAsyncIterable?.[Symbol.asyncIterator] === 'function';
}
const preset = getPreset({
connectionString: process.env.POSTGRES_URL || '',
pgSettings: (ctx) => {
if (!ctx.honov4) {
throw new Error('Hono v4 context not found');
}
const jwt = ctx.honov4.ctx.get('jwt');
return jwt ? mapJwtClaimsToPgSettings(jwt) : {};
}
});
const resolvedPreset = resolvePreset(preset);
const operations = resolvedPreset.grafserv?.persistedOperations ?? {};
const isValidRequest = (
obj: unknown
): obj is { documentId: string; variables?: Record<string, unknown> } => {
const schema = z.object({
documentId: z.string().check(z.minLength(8, 'Invalid Hash')),
variables: z.optional(z.record(z.string(), z.unknown()))
});
return schema.safeParse(obj).success;
};
export const handler = createMiddleware<HonoEnv, '/graphql'>(async (ctx) => {
const body = await ctx.req.json();
if (!isValidRequest(body)) {
throw new HTTPException(400);
}
const { documentId, variables: variableValues } = body;
const source = operations[documentId];
if (!source) {
throw new HTTPException(400, { message: 'Operation not found' });
}
const result = await grafast({
schema,
source,
variableValues,
resolvedPreset,
onError: 'NULL',
requestContext: { honov4: { ctx } }
});
try {
if (!isAsyncIterable(result)) {
// Return a promise if there's no iterator
return ctx.json(transformToRelayResponse(result));
} else {
ctx.header('Content-Type', 'multipart/mixed; boundary="-"');
return stream(ctx, async (stream) => {
const encoder = new TextEncoder();
let count = 0;
for await (const payload of result) {
// TODO: determine if needed
if (count === 0) {
void stream.write(encoder.encode('\r\n'));
void stream.write(encoder.encode('---'));
}
count++;
const transformed = transformToRelayResponse(payload);
writePartialResult(transformed, encoder, stream);
if (!payload.hasNext) {
void stream.write('--\r\n');
}
}
});
}
} catch (error) {
console.error('Error processing GraphQL request:', error);
throw new HTTPException(500, { message: 'Internal Server Error' });
}
});
const writePartialResult = (
result: ReturnType<typeof transformToRelayResponse>,
encoder: TextEncoder,
writer: StreamingApi
) => {
void writer.write(encoder.encode('\r\n'));
void writer.write(encoder.encode('Content-Type: application/json; charset=utf-8'));
void writer.write(encoder.encode('\r\n'));
const chunk = JSON.stringify(result);
const encodedChunk = encoder.encode(chunk);
void writer.write(encoder.encode(`Content-Length: ${encodedChunk.byteLength}`));
void writer.write(encoder.encode('\r\n'));
void writer.write(encoder.encode('\r\n'));
void writer.write(encodedChunk);
void writer.write(encoder.encode('\r\n'));
void writer.write(encoder.encode('---'));
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment