@uniflowed/graphql 0.0.0-alpha.2
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/index.js +113 -0
- package/package.json +25 -0
package/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
//
|
|
3
|
+
// `@uniflowed/graphql`: the Relay environment, without the boilerplate.
|
|
4
|
+
//
|
|
5
|
+
// Relay itself is `@uniflowed/relay`, which is the real `react-relay`. This is
|
|
6
|
+
// the twenty lines every Relay application writes before it can use it: a
|
|
7
|
+
// network layer that posts an operation to an endpoint, a store, and an
|
|
8
|
+
// environment holding the two together.
|
|
9
|
+
//
|
|
10
|
+
// It exists because that boilerplate is not a design decision — every project
|
|
11
|
+
// writes the same thing, gets the response shape slightly wrong the first time,
|
|
12
|
+
// and discovers it when a GraphQL error is silently rendered as `null`. uf has
|
|
13
|
+
// an opinion about it, and the opinion is small enough to read.
|
|
14
|
+
//
|
|
15
|
+
// It is deliberately not a wrapper. `createEnvironment` hands back Relay's own
|
|
16
|
+
// `Environment`, so everything Relay can do a uf project can do, and a project
|
|
17
|
+
// that outgrows this function replaces the call rather than escaping a
|
|
18
|
+
// framework. That is red line 8, and this is what it looks like when it holds.
|
|
19
|
+
|
|
20
|
+
import type { FetchClient } from "@uniflowed/fetch";
|
|
21
|
+
import { Environment, Network, Observable, RecordSource, Store } from "relay-runtime";
|
|
22
|
+
|
|
23
|
+
export type { Environment } from "relay-runtime";
|
|
24
|
+
|
|
25
|
+
/** A GraphQL error as a server reports it. */
|
|
26
|
+
export type GraphQlError = {
|
|
27
|
+
readonly message: string,
|
|
28
|
+
readonly path?: $ReadOnlyArray<string | number>,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Raised when a response carries `errors`. */
|
|
32
|
+
export class GraphQlResponseError extends Error {
|
|
33
|
+
/** Every error the response reported, in the order it reported them. */
|
|
34
|
+
errors: $ReadOnlyArray<GraphQlError>;
|
|
35
|
+
|
|
36
|
+
constructor(errors: $ReadOnlyArray<GraphQlError>) {
|
|
37
|
+
const first = errors[0]?.message ?? "the server reported an error";
|
|
38
|
+
super(errors.length === 1 ? first : `${first} (and ${errors.length - 1} more)`);
|
|
39
|
+
this.name = "GraphQlResponseError";
|
|
40
|
+
this.errors = errors;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** How to reach the GraphQL endpoint. */
|
|
45
|
+
export type EnvironmentOptions = {
|
|
46
|
+
/** Where operations are posted. */
|
|
47
|
+
readonly endpoint: string,
|
|
48
|
+
/**
|
|
49
|
+
* The fetch client to post with.
|
|
50
|
+
*
|
|
51
|
+
* Explicit rather than reaching for a global: uf does not override
|
|
52
|
+
* `globalThis.fetch`, and a server rendering a request often needs to forward
|
|
53
|
+
* that request's credentials, which a global cannot know about.
|
|
54
|
+
*/
|
|
55
|
+
readonly fetch: FetchClient,
|
|
56
|
+
/** Headers sent with every operation, e.g. an authorization token. */
|
|
57
|
+
readonly headers?: { readonly [string]: string },
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A Relay environment that posts operations to `endpoint`.
|
|
62
|
+
*
|
|
63
|
+
* The store is fresh, so two calls are two independent caches — which is what a
|
|
64
|
+
* server needs, where one environment per request is the only way two users do
|
|
65
|
+
* not see each other's data.
|
|
66
|
+
*/
|
|
67
|
+
export function createEnvironment(options: EnvironmentOptions): Environment {
|
|
68
|
+
return new Environment({
|
|
69
|
+
network: Network.create(fetchOperation(options)),
|
|
70
|
+
store: new Store(new RecordSource()),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The network function Relay calls to run one operation.
|
|
76
|
+
*
|
|
77
|
+
* Returns a promise rather than an `Observable`: Relay accepts either, and a
|
|
78
|
+
* promise is the whole of what a plain HTTP endpoint can do. Subscriptions need
|
|
79
|
+
* the observable form and a transport that supports them, which is a different
|
|
80
|
+
* function and not one uf guesses at.
|
|
81
|
+
*/
|
|
82
|
+
function fetchOperation(options: EnvironmentOptions) {
|
|
83
|
+
return async function run(operation: mixed, variables: mixed): Promise<mixed> {
|
|
84
|
+
const request = operation as $FlowFixMe;
|
|
85
|
+
// `raw` rather than `request`: a GraphQL endpoint answers 200 with an
|
|
86
|
+
// `errors` array, so the status is not the outcome and the body has to be
|
|
87
|
+
// read either way.
|
|
88
|
+
const response = await options.fetch.raw(options.endpoint, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: {
|
|
91
|
+
accept: "application/graphql-response+json, application/json",
|
|
92
|
+
...(options.headers ?? {}),
|
|
93
|
+
},
|
|
94
|
+
body: {
|
|
95
|
+
query: request.text,
|
|
96
|
+
variables,
|
|
97
|
+
// Relay names every operation, and sending the name makes a server's
|
|
98
|
+
// logs and traces readable without any extra work.
|
|
99
|
+
operationName: request.name,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const payload = (await response.json()) as $FlowFixMe;
|
|
104
|
+
// A GraphQL response can carry `errors` and still be HTTP 200, and a
|
|
105
|
+
// client that only checks the status renders `null` and says nothing.
|
|
106
|
+
if (Array.isArray(payload?.errors) && payload.errors.length > 0) {
|
|
107
|
+
throw new GraphQlResponseError(payload.errors);
|
|
108
|
+
}
|
|
109
|
+
return payload;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export { Observable };
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uniflowed/graphql",
|
|
3
|
+
"version": "0.0.0-alpha.2",
|
|
4
|
+
"description": "The Relay environment, without the boilerplate, for the Unified Toolchain for Flow.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ubugeeei-prod/uf.git",
|
|
11
|
+
"directory": "packages/graphql"
|
|
12
|
+
},
|
|
13
|
+
"exports": {
|
|
14
|
+
".": "./index.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"index.js"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@uniflowed/fetch": "0.0.0-alpha.2"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"relay-runtime": ">=20"
|
|
24
|
+
}
|
|
25
|
+
}
|