summaryrefslogtreecommitdiff
path: root/src/http.ts
blob: c8bfa0fe190fffe6dc595f838f6aafb3a53e90ff (plain) (blame)
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
/// util

import { runHttpQuery } from "./common.ts";

import type { GQLOptions, GQLRequest, GraphQLParams } from "./utility/types.ts";



/// export

export function GraphQLHTTP<
  Req extends GQLRequest = GQLRequest,
  Ctx extends { request: Req } = { request: Req }>({
    playgroundOptions = {},
    headers = {},
    ...options
  }: GQLOptions<Ctx, Req>) {
  /**
   * Create a new GraphQL HTTP middleware with schema, context etc
   * @param {GQLOptions} options
   *
   * @example
   * ```ts
   * const graphql = await GraphQLHTTP({ schema })
   *
   * for await (const req of s) graphql(req)
   * ```
   */

  return async (request: Req) => {
    const accept = request.headers.get("Accept") || "";

    const typeList = ["application/json", "text/html", "text/plain", "*/*"]
      .map(contentType => ({
        contentType,
        index: accept.indexOf(contentType)
      }))
      .filter(({ index }) => index >= 0)
      .sort((a, b) => a.index - b.index)
      .map(({ contentType }) => contentType);

    if (accept && !typeList.length) {
      return new Response("Not Acceptable", {
        headers: new Headers(headers),
        status: 406
      });
    } else if (!["GET", "PUT", "POST", "PATCH"].includes(request.method)) {
      return new Response("Method Not Allowed", {
        headers: new Headers(headers),
        status: 405
      });
    }

    let params: Promise<GraphQLParams>;

    if (request.method === "GET") {
      const urlQuery = request.url.substring(request.url.indexOf("?"));
      const queryParams = new URLSearchParams(urlQuery);

      if (options.graphiql && typeList[0] === "text/html" && !queryParams.has("raw")) {
        const { renderPlaygroundPage } = await import("./graphiql/render.ts");

        const playground = renderPlaygroundPage({
          ...playgroundOptions,
          endpoint: "/graphql"
        });

        return new Response(playground, {
          headers: new Headers({
            "Content-Type": "text/html",
            ...headers
          })
        });
      } else if (typeList.length === 1 && typeList[0] === "text/html") {
        return new Response("Not Acceptable", {
          headers: new Headers(headers),
          status: 406
        });
      } else if (queryParams.has("query")) {
        params = Promise.resolve({ query: queryParams.get("query") } as GraphQLParams);
      } else {
        params = Promise.reject(new Error("No query given!"));
      }
    } else if (typeList.length === 1 && typeList[0] === "text/html") {
      return new Response("Not Acceptable", {
        headers: new Headers(headers),
        status: 406
      });
    } else {
      params = request.json();
    }

    try {
      const result = await runHttpQuery<Req, Ctx>(await params, options, { request });
      let contentType = "text/plain";

      if (!typeList.length || typeList.includes("application/json") || typeList.includes("*/*"))
        contentType = "application/json";

      return new Response(JSON.stringify(result, null, 2), {
        headers: new Headers({
          "Content-Type": contentType,
          ...headers
        }),
        status: 200
      });
    } catch(e) {
      console.error(e);

      return new Response(
        "Malformed Request " + (request.method === "GET" ? "Query" : "Body"), {
          headers: new Headers(headers),
          status: 400
        }
      );
    }
  }
}