release: WRNexusJS 0.8.0
Quality / quality (ubuntu-latest) (push) Failing after 21s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-02 23:18:51 +05:30
parent 87507edf59
commit 586a6db8ff
625 changed files with 243608 additions and 11210 deletions
+88
View File
@@ -0,0 +1,88 @@
export interface GraphqlRequest {
query: string;
variables?: Record<string, unknown>;
operationName?: string;
}
export interface GraphqlExecutionResult {
data?: unknown;
errors?: Array<{ message: string; [key: string]: unknown }>;
}
export interface GraphqlOptions {
execute(
request: GraphqlRequest,
context: { request: Request; signal: AbortSignal },
): GraphqlExecutionResult | Promise<GraphqlExecutionResult>;
maxQueryBytes?: number;
maxDepth?: number;
maxAliases?: number;
allowIntrospection?: boolean;
}
function queryMetrics(query: string) {
let depth = 0,
maxDepth = 0,
aliases = 0;
let string = false,
escaped = false;
for (let index = 0; index < query.length; index++) {
const char = query[index]!;
if (string) {
if (escaped) escaped = false;
else if (char === "\\") escaped = true;
else if (char === '"') string = false;
continue;
}
if (char === '"') string = true;
else if (char === "{") maxDepth = Math.max(maxDepth, ++depth);
else if (char === "}") depth--;
else if (char === ":" && /[A-Za-z0-9_]\s*$/.test(query.slice(Math.max(0, index - 40), index)))
aliases++;
}
return { depth: maxDepth, aliases };
}
export function createGraphqlHandler(options: GraphqlOptions) {
return async (request: Request): Promise<Response> => {
if (request.method !== "POST")
return new Response("Method Not Allowed", { status: 405, headers: { allow: "POST" } });
const maxBytes = options.maxQueryBytes ?? 100_000;
const length = Number(request.headers.get("content-length") ?? 0);
if (length > maxBytes)
return Response.json(
{ errors: [{ message: "GraphQL query is too large" }] },
{ status: 413 },
);
const body = (await request.json().catch(() => null)) as GraphqlRequest | null;
if (
!body ||
typeof body.query !== "string" ||
new TextEncoder().encode(body.query).length > maxBytes
)
return Response.json(
{ errors: [{ message: "A bounded GraphQL query string is required" }] },
{ status: 400 },
);
if (options.allowIntrospection === false && /\b__(?:schema|type)\b/.test(body.query))
return Response.json(
{ errors: [{ message: "GraphQL introspection is disabled" }] },
{ status: 403 },
);
const metrics = queryMetrics(body.query);
if (metrics.depth > (options.maxDepth ?? 12) || metrics.aliases > (options.maxAliases ?? 50))
return Response.json(
{ errors: [{ message: "GraphQL query complexity limit exceeded" }] },
{ status: 400 },
);
try {
const result = await options.execute(body, { request, signal: request.signal });
return Response.json(result, {
status: result.errors?.length && result.data === undefined ? 400 : 200,
headers: { "cache-control": "no-store" },
});
} catch {
return Response.json({ errors: [{ message: "GraphQL execution failed" }] }, { status: 500 });
}
};
}
export { graphqlPlugin } from "./plugin.ts";
+24
View File
@@ -0,0 +1,24 @@
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import type { WrnexusPlugin } from "@wrnexus/plugin";
import { createGraphqlHandler, type GraphqlOptions } from "./index.ts";
export function graphqlPlugin(options: GraphqlOptions & { path?: string }): WrnexusPlugin {
return {
name: "@wrnexus/graphql",
version: "0.8.0",
setup() {
(globalThis as any).__wrnexusGraphqlHandler = createGraphqlHandler(options);
},
routeEntries: [
{
kind: "api",
path: options.path ?? "/api/graphql",
entry: join(dirname(fileURLToPath(import.meta.url)), "route.ts"),
},
],
documentation: ["packages/graphql/README.md"],
typeDefinitions: ["packages/graphql/src/index.ts"],
};
}
export default graphqlPlugin;
+6
View File
@@ -0,0 +1,6 @@
export async function POST(ctx: { req: Request }): Promise<Response> {
const handler = (globalThis as any).__wrnexusGraphqlHandler;
if (typeof handler !== "function")
return new Response("GraphQL is not configured", { status: 503 });
return handler(ctx.req);
}