forked from apollographql/apollo-client-devtools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlinks.js
More file actions
executable file
·211 lines (187 loc) · 6.78 KB
/
links.js
File metadata and controls
executable file
·211 lines (187 loc) · 6.78 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import { ApolloClient } from "apollo-client";
import { execute, ApolloLink, Observable, from } from "apollo-link";
import gql from "graphql-tag";
import { buildSchemasFromTypeDefs } from "./typeDefs";
const hasOwn = Object.prototype.hasOwnProperty;
/*
* Supports dynamic client schemas set on the context;
* schemas must be an array of the following shape:
* {
* definition: schemaString,
* directives: `directive @client on FIELD`,
* }
*/
const apolloClientSchema = {
directives: "directive @connection(key: String!, filter: [String]) on FIELD",
};
const schemaLink = () =>
new ApolloLink((operation, forward) => {
const obs = forward(operation);
return obs.map
? obs.map(result => {
let { schemas = [] } = operation.getContext();
result.extensions = Object.assign({}, result.extensions, {
schemas: schemas.concat([apolloClientSchema]),
});
return result;
})
: obs;
});
// Forward all "errors" to next with a good shape for graphiql
const errorLink = () =>
new ApolloLink((operation, forward) => {
return new Observable(observer => {
let sub;
try {
sub = forward(operation).subscribe({
next: observer.next.bind(observer),
error: networkError => {
observer.next({
errors: [
{
message: networkError.message,
locations: [networkError.stack],
},
],
});
},
complete: observer.complete.bind(observer),
});
} catch (e) {
observer.next({
errors: [{ message: e.message, locations: [e.stack] }],
});
}
return () => {
if (sub) sub.unsubscribe();
};
});
});
const cacheLink = fetchPolicy =>
new ApolloLink((operation, forward) => {
if (fetchPolicy === "no-cache") return forward(operation);
const { cache } = operation.getContext();
const { variables, query } = operation;
try {
const results = cache.readQuery({ query, variables });
if (results) return Observable.of({ data: results });
} catch (e) {}
return forward(operation);
});
export const initLinkEvents = (hook, bridge) => {
// Handle incoming requests
const subscriber = request => {
const { query, variables, operationName, key, fetchPolicy } = JSON.parse(
request,
);
try {
const apolloClient = hook.ApolloClient;
const userLink = apolloClient.link;
const cache = apolloClient.cache;
let operationExecution$;
const queryAst = gql(query);
const subscriptionHandlers = {
next(data) {
bridge.send(`link:next:${key}`, JSON.stringify(data));
},
error(err) {
bridge.send(`link:error:${key}`, JSON.stringify(err));
},
complete: () => bridge.send(`link:complete:${key}`),
};
// Devtools can currently be used with 2 versions of local state
// handling: 1) Using `apollo-link-state` or 2) Using local state
// features integrated directly into Apollo Client. `apollo-link-state`
// will eventually be deprecated, but for the time being we need to
// support both approaches via devtools.
//
// The `apollo-link-state` approach uses a custom link chain to parse
// and execute queries, whereas the Apollo Client local state approach
// uses Apollo Client directly. To decide which approach to use
// below, we'll check to see if typeDefs have been set on the
// ApolloClient instance, as if so, this means Apollo Client local state
// is being used.
const supportsApolloClientLocalState =
hasOwn.call(apolloClient, "typeDefs");
if (!supportsApolloClientLocalState) {
// Supports `apollo-link-state`.
const context = { __devtools_key__: key, cache };
const devtoolsLink = from([
errorLink(),
cacheLink(fetchPolicy),
schemaLink(),
userLink,
]);
const operationExecution$ = execute(devtoolsLink, {
query: queryAst,
variables,
operationName,
context,
});
operationExecution$.subscribe(subscriptionHandlers);
return;
}
// Supports Apollo Client local state.
const typeDefs = apolloClient.typeDefs;
// When using `apollo-link-state`, client supplied typeDefs (for a
// local only schema) are extracted by re-using the same Apollo Link
// chain as the application, along with the `schemaLink` mentioned
// above. When using the local state functionality built directly into
// Apollo Client however, the logic to extract and store the schema
// from typeDefs that is contained within `apollo-link-state` is not
// called. To address this, we'll generate the local schema
// using typeDefs pulled out of the current AC instance.
const schemas = buildSchemasFromTypeDefs(typeDefs);
// Create a new ApolloClient instance (that re-uses parts of the
// user land application ApolloClient instance) to avoid having
// devtools IntrospectionQuery's (and potentially other future devtool
// only queries) show up in the "Queries" panel as watched queries.
// This means devtools specific queries will use their own query store.
const apolloClientReplica = new ApolloClient({
link: userLink,
cache,
typeDefs,
resolvers: apolloClient.getResolvers(),
});
if (
queryAst.definitions &&
queryAst.definitions.length > 0 &&
queryAst.definitions[0].operation === "mutation"
) {
operationExecution$ = new Observable(observer => {
apolloClientReplica
.mutate({
mutation: queryAst,
variables,
})
.then(result => {
observer.next(result);
});
});
} else {
operationExecution$ = apolloClientReplica.watchQuery({
query: queryAst,
variables,
fetchPolicy,
});
}
operationExecution$.subscribe(
Object.assign({}, subscriptionHandlers, {
next(data) {
// `apollo-link-state` gets the local schema added to the result
// via the `schemaLink`, but Apollo Client local state does not.
// When using Apollo Client local state, we'll add the schema
// manually.
data.extensions = Object.assign({}, data.extensions, {
schemas: [...schemas, apolloClientSchema],
});
bridge.send(`link:next:${key}`, JSON.stringify(data));
},
}),
);
} catch (e) {
bridge.send(`link:error:${key}`, JSON.stringify(e));
}
};
bridge.on("link:operation", subscriber);
};