-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.ts
143 lines (120 loc) · 3.36 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { byteLength } from "byte-length";
import { graphql, GraphQLArgs, GraphQLError } from "graphql";
import { parse } from "content-type";
/**
* GraphQL args provided by the HTTP request.
*/
export type RequestArgs = Pick<
GraphQLArgs,
"source" | "operationName" | "variableValues"
>;
/**
* Parse parameters from URL search parameters.
*/
function getArgsFromParams(params: URLSearchParams): RequestArgs {
const variables = params.get("variables");
return {
source: params.get("query") || "",
operationName: params.get("operationName"),
variableValues: variables ? JSON.parse(variables) : undefined,
};
}
/**
* Get variables from URL (e.g. GET request).
*/
export async function getArgsFromURL(req: Request): Promise<RequestArgs> {
const url = new URL(req.url);
return getArgsFromParams(url.searchParams);
}
/**
* Get variables from HTTP body (e.g. POST request).
*/
export async function getArgsFromBody(req: Request): Promise<RequestArgs> {
const contentType = req.headers.get("Content-Type");
if (contentType === null) {
return { source: "" };
}
const media = parse(contentType);
if (media.type === "application/graphql") {
const body = await req.text();
return { source: body };
}
if (media.type === "application/json") {
const body = await req.json();
return {
source: typeof body.query === "string" ? body.query : "",
operationName:
typeof body.operationName === "string" ? body.operationName : null,
variableValues: body.variables,
};
}
if (media.type === "application/x-www-form-urlencoded") {
const body = await req.text();
const params = new URLSearchParams(body);
return getArgsFromParams(params);
}
return { source: "" };
}
/**
* Execute the GraphQL schema.
*/
async function exec(options: GraphQLArgs & ProcessOptions) {
const { data, errors } = await graphql(options);
const body = JSON.stringify({
data,
errors: errors?.map((x) =>
options.formatError ? options.formatError(x) : x
),
});
return new Response(body, {
headers: {
"Content-Type": "application/json",
"Content-Length": String(byteLength(body)),
},
});
}
/**
* Configuration options for processing GraphQL.
*/
export type ProcessOptions = {
formatError?: (error: GraphQLError) => { message: string };
};
/**
* Configuration options for handler.
*/
export type Options = Omit<GraphQLArgs, keyof RequestArgs> & ProcessOptions;
/**
* Process GraphQL request using the URL (e.g. GET).
*/
export async function processGraphQLFromURL(req: Request, args: Options) {
return exec({ ...args, ...(await getArgsFromURL(req)) });
}
/**
* Process GraphQL request using the request body (e.g. POST).
*/
export async function processGraphQLFromBody(req: Request, args: Options) {
return exec({ ...args, ...(await getArgsFromBody(req)) });
}
/**
* Create a request handler for GraphQL.
*/
export async function processGraphQL(
req: Request,
args: Options
): Promise<Response> {
const method = req.method.toUpperCase();
try {
if (method === "GET") {
return await processGraphQLFromURL(req, args);
}
if (method === "POST") {
return await processGraphQLFromBody(req, args);
}
} catch (err) {
return new Response(null, { status: 400 });
}
return new Response(null, {
status: 405,
headers: { Allow: "GET,POST" },
});
}