@fougere/adapter-graphql 0.3.0-alpha.0 → 0.5.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/auto-register.d.ts +3 -42
- package/dist/auto-register.d.ts.map +1 -1
- package/dist/auto-register.js +9 -12
- package/dist/auto-register.js.map +1 -1
- package/dist/pothos.d.ts +6 -6
- package/dist/pothos.d.ts.map +1 -1
- package/dist/pothos.js +13 -15
- package/dist/pothos.js.map +1 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +48 -48
- package/dist/serve.js.map +1 -1
- package/package.json +5 -4
- package/src/app.ts +73 -0
- package/src/auto-register.ts +455 -0
- package/src/index.ts +14 -0
- package/src/pothos-entry.ts +9 -0
- package/src/pothos.ts +1011 -0
- package/src/serve.ts +134 -0
package/src/serve.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve a GraphQL schema on an HttpRouter — no Apollo, no Yoga needed.
|
|
3
|
+
*
|
|
4
|
+
* Uses the `graphql` package directly for execution.
|
|
5
|
+
* GraphiQL is available as an explicit opt-in for trusted development environments.
|
|
6
|
+
*/
|
|
7
|
+
import type { HttpRouter, ResponseResult } from '@fougere/http';
|
|
8
|
+
import { getOperationAST, graphql, parse, type GraphQLSchema } from 'graphql';
|
|
9
|
+
|
|
10
|
+
export interface GraphQLServeOptions {
|
|
11
|
+
/** Route path. Default: '/graphql'. */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** Enable GraphiQL playground on GET requests from browsers. Default: false. */
|
|
14
|
+
playground?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What a GraphQL request is, wherever the method read it from. */
|
|
18
|
+
interface GraphQLRequest {
|
|
19
|
+
query: string;
|
|
20
|
+
variables?: Record<string, unknown>;
|
|
21
|
+
operationName?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function graphiqlHtml(path: string): string {
|
|
25
|
+
return `<!DOCTYPE html>
|
|
26
|
+
<html lang="en">
|
|
27
|
+
<head>
|
|
28
|
+
<meta charset="utf-8" />
|
|
29
|
+
<title>GraphiQL — Fougere</title>
|
|
30
|
+
<link rel="stylesheet" href="https://unpkg.com/graphiql@3/graphiql.min.css" />
|
|
31
|
+
<style>body{margin:0;height:100vh}#graphiql{height:100vh}</style>
|
|
32
|
+
</head>
|
|
33
|
+
<body>
|
|
34
|
+
<div id="graphiql"></div>
|
|
35
|
+
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
|
|
36
|
+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
|
|
37
|
+
<script src="https://unpkg.com/graphiql@3/graphiql.min.js" crossorigin></script>
|
|
38
|
+
<script>
|
|
39
|
+
const fetcher = GraphiQL.createFetcher({ url: '${path}' });
|
|
40
|
+
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
|
|
41
|
+
root.render(React.createElement(GraphiQL, { fetcher }));
|
|
42
|
+
</script>
|
|
43
|
+
</body>
|
|
44
|
+
</html>`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a GraphQL endpoint on an HttpRouter.
|
|
49
|
+
*
|
|
50
|
+
* - POST: execute queries/mutations
|
|
51
|
+
* - GET with `query` param: execute queries
|
|
52
|
+
* - GET from browser (no `query` param): serve GraphiQL only with `playground: true`
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { registerGraphQL } from '@fougere/adapter-graphql'
|
|
56
|
+
*
|
|
57
|
+
* registerGraphQL(router, schema, { playground: process.env.NODE_ENV === 'development' })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function registerGraphQL(
|
|
61
|
+
router: HttpRouter,
|
|
62
|
+
schema: GraphQLSchema,
|
|
63
|
+
options?: GraphQLServeOptions,
|
|
64
|
+
): void {
|
|
65
|
+
const path = options?.path ?? '/graphql';
|
|
66
|
+
const playground = options?.playground ?? false;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The one place this file states what a GraphQL request answers.
|
|
70
|
+
*
|
|
71
|
+
* A GraphQL error is not an HTTP error — the transport succeeded and the errors ride
|
|
72
|
+
* in the body — so every executed request is 200 whatever the resolvers said. The two
|
|
73
|
+
* methods differ in where they READ the request, never in how they answer it.
|
|
74
|
+
*/
|
|
75
|
+
const answer = async (request: GraphQLRequest): Promise<ResponseResult> => ({
|
|
76
|
+
status: 200,
|
|
77
|
+
data: await graphql({
|
|
78
|
+
schema,
|
|
79
|
+
source: request.query,
|
|
80
|
+
variableValues: request.variables,
|
|
81
|
+
operationName: request.operationName,
|
|
82
|
+
}),
|
|
83
|
+
headers: { 'content-type': 'application/json' },
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
router.on('POST', path, async (ctx) => {
|
|
87
|
+
const body = (await ctx.body()) as Partial<GraphQLRequest> | undefined;
|
|
88
|
+
|
|
89
|
+
if (!body?.query) {
|
|
90
|
+
return { status: 400, data: { errors: [{ message: 'Missing query' }] } };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return answer(body as GraphQLRequest);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
router.on('GET', path, async (ctx): Promise<ResponseResult> => {
|
|
97
|
+
// No query param → GraphiQL, when it was asked for.
|
|
98
|
+
if (!ctx.query.query) {
|
|
99
|
+
return playground
|
|
100
|
+
? {
|
|
101
|
+
status: 200,
|
|
102
|
+
data: graphiqlHtml(path),
|
|
103
|
+
headers: { 'content-type': 'text/html' },
|
|
104
|
+
raw: true,
|
|
105
|
+
}
|
|
106
|
+
: { status: 400, data: { errors: [{ message: 'Missing query parameter' }] } };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let variables: Record<string, unknown> | undefined;
|
|
110
|
+
let document;
|
|
111
|
+
try {
|
|
112
|
+
variables = ctx.query.variables ? JSON.parse(ctx.query.variables) : undefined;
|
|
113
|
+
document = parse(ctx.query.query);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return { status: 400, data: { errors: [{ message: (err as Error).message }] } };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// GET is the cacheable half of the protocol, so a mutation is refused rather than run.
|
|
119
|
+
const operation = getOperationAST(document, ctx.query.operationName);
|
|
120
|
+
if (operation && operation.operation !== 'query') {
|
|
121
|
+
return {
|
|
122
|
+
status: 405,
|
|
123
|
+
data: { errors: [{ message: 'Only GraphQL queries may be executed over GET' }] },
|
|
124
|
+
headers: { allow: 'POST' },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return answer({
|
|
129
|
+
query: ctx.query.query,
|
|
130
|
+
variables,
|
|
131
|
+
operationName: ctx.query.operationName,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
}
|