@webda/graphql 4.0.0-beta.2 → 4.0.0-beta.3
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 +131 -9
- package/lib/graphql.service.d.ts +205 -0
- package/lib/graphql.service.d.ts.map +1 -0
- package/lib/graphql.service.js +1088 -0
- package/lib/graphql.service.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/types/any.d.ts.map +1 -1
- package/lib/types/any.js +5 -0
- package/lib/types/any.js.map +1 -1
- package/lib/types/long.d.ts.map +1 -1
- package/lib/types/long.js +10 -0
- package/lib/types/long.js.map +1 -1
- package/package.json +33 -46
- package/webda.module.json +124 -126
- package/lib/graphql.d.ts +0 -173
- package/lib/graphql.d.ts.map +0 -1
- package/lib/graphql.js +0 -1005
- package/lib/graphql.js.map +0 -1
- package/lib/graphql.spec.d.ts +0 -2
- package/lib/graphql.spec.d.ts.map +0 -1
- package/lib/graphql.spec.js +0 -669
- package/lib/graphql.spec.js.map +0 -1
package/lib/graphql.js
DELETED
|
@@ -1,1005 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
import { Core, DomainService, DomainServiceParameters, Route, WebdaError } from "@webda/core";
|
|
8
|
-
import * as WebdaQL from "@webda/ql";
|
|
9
|
-
import { EventIterator, MergedIterator } from "@webda/runtime";
|
|
10
|
-
import { GraphQLBoolean, GraphQLError, GraphQLInputObjectType, GraphQLList, GraphQLObjectType, GraphQLSchema, GraphQLString, printSchema } from "graphql";
|
|
11
|
-
import { createHandler } from "graphql-http";
|
|
12
|
-
import { CloseCode, makeServer } from "graphql-ws";
|
|
13
|
-
import { nextTick } from "process";
|
|
14
|
-
import { WebSocketServer } from "ws";
|
|
15
|
-
import { AnyScalarType } from "./types/any.js";
|
|
16
|
-
import { DateScalar } from "./types/date.js";
|
|
17
|
-
import { GraphQLLong } from "./types/long.js";
|
|
18
|
-
const GraphIQL = `
|
|
19
|
-
<!doctype html>
|
|
20
|
-
<html lang="en">
|
|
21
|
-
<head>
|
|
22
|
-
<title>GraphiQL</title>
|
|
23
|
-
<style>
|
|
24
|
-
body {
|
|
25
|
-
height: 100%;
|
|
26
|
-
margin: 0;
|
|
27
|
-
width: 100%;
|
|
28
|
-
overflow: hidden;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
#graphiql {
|
|
32
|
-
height: 100vh;
|
|
33
|
-
}
|
|
34
|
-
</style>
|
|
35
|
-
<!--
|
|
36
|
-
This GraphiQL example depends on Promise and fetch, which are available in
|
|
37
|
-
modern browsers, but can be "polyfilled" for older browsers.
|
|
38
|
-
GraphiQL itself depends on React DOM.
|
|
39
|
-
If you do not want to rely on a CDN, you can host these files locally or
|
|
40
|
-
include them directly in your favored resource bundler.
|
|
41
|
-
-->
|
|
42
|
-
<script
|
|
43
|
-
crossorigin
|
|
44
|
-
src="https://unpkg.com/react@18/umd/react.development.js"
|
|
45
|
-
></script>
|
|
46
|
-
<script
|
|
47
|
-
crossorigin
|
|
48
|
-
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
|
|
49
|
-
></script>
|
|
50
|
-
<!--
|
|
51
|
-
These two files can be found in the npm module, however you may wish to
|
|
52
|
-
copy them directly into your environment, or perhaps include them in your
|
|
53
|
-
favored resource bundler.
|
|
54
|
-
-->
|
|
55
|
-
<script
|
|
56
|
-
src="https://unpkg.com/graphiql/graphiql.min.js"
|
|
57
|
-
type="application/javascript"
|
|
58
|
-
></script>
|
|
59
|
-
<link rel="stylesheet" href="https://unpkg.com/graphiql/graphiql.min.css" />
|
|
60
|
-
<!--
|
|
61
|
-
These are imports for the GraphIQL Explorer plugin.
|
|
62
|
-
-->
|
|
63
|
-
<script
|
|
64
|
-
src="https://unpkg.com/@graphiql/plugin-explorer/dist/index.umd.js"
|
|
65
|
-
crossorigin
|
|
66
|
-
></script>
|
|
67
|
-
<script
|
|
68
|
-
src="https://unpkg.com/graphql-ws/umd/graphql-ws.min.js"
|
|
69
|
-
type="application/javascript"
|
|
70
|
-
></script>
|
|
71
|
-
<link
|
|
72
|
-
rel="stylesheet"
|
|
73
|
-
href="https://unpkg.com/@graphiql/plugin-explorer/dist/style.css"
|
|
74
|
-
/>
|
|
75
|
-
</head>
|
|
76
|
-
|
|
77
|
-
<body>
|
|
78
|
-
<div id="graphiql">Loading...</div>
|
|
79
|
-
<script>
|
|
80
|
-
const root = ReactDOM.createRoot(document.getElementById('graphiql'));
|
|
81
|
-
const fetcher = GraphiQL.createFetcher({
|
|
82
|
-
url: '{{URL}}',
|
|
83
|
-
subscriptionUrl: '{{WSURL}}',
|
|
84
|
-
headers: { 'X-GraphiQL-Schema': 'true' },
|
|
85
|
-
});
|
|
86
|
-
const explorerPlugin = GraphiQLPluginExplorer.explorerPlugin();
|
|
87
|
-
root.render(
|
|
88
|
-
React.createElement(GraphiQL, {
|
|
89
|
-
fetcher,
|
|
90
|
-
defaultEditorToolsVisibility: true,
|
|
91
|
-
plugins: [explorerPlugin],
|
|
92
|
-
}),
|
|
93
|
-
);
|
|
94
|
-
</script>
|
|
95
|
-
</body>
|
|
96
|
-
</html>
|
|
97
|
-
`;
|
|
98
|
-
/**
|
|
99
|
-
* Parameters for the GraphQL service
|
|
100
|
-
*/
|
|
101
|
-
export class GraphQLParameters extends DomainServiceParameters {
|
|
102
|
-
constructor(params) {
|
|
103
|
-
super({ ...params, nameTransfomer: params.nameTransfomer || "PascalCase" });
|
|
104
|
-
this.url ?? (this.url = "/graphql");
|
|
105
|
-
this.maxOperationsPerRequest ?? (this.maxOperationsPerRequest = 10);
|
|
106
|
-
this.userModel ?? (this.userModel = "User");
|
|
107
|
-
this.exposeMe ?? (this.exposeMe = true);
|
|
108
|
-
this.globalSubscription ?? (this.globalSubscription = true);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Implement a GraphQL service
|
|
113
|
-
*
|
|
114
|
-
* It will expose all your exposed models as GraphQL types
|
|
115
|
-
* Dynamically generate the schema
|
|
116
|
-
*
|
|
117
|
-
* @WebdaModda
|
|
118
|
-
*/
|
|
119
|
-
export class GraphQLService extends DomainService {
|
|
120
|
-
loadParameters(params) {
|
|
121
|
-
return new GraphQLParameters(params);
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
*
|
|
125
|
-
* @param prop
|
|
126
|
-
* @param definitions
|
|
127
|
-
* @returns
|
|
128
|
-
*/
|
|
129
|
-
getJsonSchemaDefinition(prop, definitions = {}) {
|
|
130
|
-
if (prop?.type) {
|
|
131
|
-
if (prop.type === "array" && prop.items["$ref"]) {
|
|
132
|
-
prop.items = this.getJsonSchemaDefinition(prop.items, definitions);
|
|
133
|
-
}
|
|
134
|
-
return prop;
|
|
135
|
-
}
|
|
136
|
-
while (prop && prop.$ref) {
|
|
137
|
-
prop = definitions[decodeURIComponent(prop.$ref.replace("#/definitions/", ""))];
|
|
138
|
-
}
|
|
139
|
-
return prop || { type: "null" };
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
*
|
|
143
|
-
* @param schema
|
|
144
|
-
* @param defaultName
|
|
145
|
-
* @returns
|
|
146
|
-
*/
|
|
147
|
-
getGraphQLSchemaFromSchema(schema, defaultName, input) {
|
|
148
|
-
let type;
|
|
149
|
-
if (!schema || !schema.type) {
|
|
150
|
-
type = AnyScalarType;
|
|
151
|
-
}
|
|
152
|
-
else if (schema.type === "string") {
|
|
153
|
-
if (schema.format === "date-time") {
|
|
154
|
-
type = DateScalar;
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
type = GraphQLString;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
else if (schema.type === "boolean") {
|
|
161
|
-
type = GraphQLBoolean;
|
|
162
|
-
}
|
|
163
|
-
else if (schema.type === "array") {
|
|
164
|
-
type = new GraphQLList(this.getGraphQLSchemaFromSchema(schema.items, schema.title || defaultName, input)?.type ||
|
|
165
|
-
GraphQLString);
|
|
166
|
-
}
|
|
167
|
-
else if (schema.type === "integer" || schema.type === "number") {
|
|
168
|
-
// Use long to handle 2^52
|
|
169
|
-
type = GraphQLLong;
|
|
170
|
-
}
|
|
171
|
-
else if (schema.type === "null") {
|
|
172
|
-
return undefined;
|
|
173
|
-
}
|
|
174
|
-
else if (schema.type === "object") {
|
|
175
|
-
const fields = {};
|
|
176
|
-
// Map does not fit well with graphql
|
|
177
|
-
if (!schema.properties) {
|
|
178
|
-
return { type: AnyScalarType, description: "Map" };
|
|
179
|
-
}
|
|
180
|
-
for (const i in schema.properties) {
|
|
181
|
-
const res = this.getGraphQLSchemaFromSchema(this.getJsonSchemaDefinition(schema.properties[i], schema.definitions), `${schema.title || defaultName}_${i}`, input);
|
|
182
|
-
if (!res)
|
|
183
|
-
continue;
|
|
184
|
-
fields[i] = res;
|
|
185
|
-
}
|
|
186
|
-
if (Object.keys(fields).length === 0) {
|
|
187
|
-
this.log("DEBUG", "Return map for", defaultName, "because no fields");
|
|
188
|
-
return { type: AnyScalarType, description: "Map" };
|
|
189
|
-
}
|
|
190
|
-
type = new (input ? GraphQLInputObjectType : GraphQLObjectType)({
|
|
191
|
-
fields,
|
|
192
|
-
name: schema.title || defaultName
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
return { type, description: schema.description };
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Add the GraphQL schema results
|
|
199
|
-
* @param type
|
|
200
|
-
* @returns
|
|
201
|
-
*/
|
|
202
|
-
getGraphQLQueryResult(type) {
|
|
203
|
-
var _a;
|
|
204
|
-
const name = `${type.name}QueryResult`;
|
|
205
|
-
(_a = this.modelsMap)[name] ?? (_a[name] = new GraphQLObjectType({
|
|
206
|
-
fields: {
|
|
207
|
-
results: {
|
|
208
|
-
type: new GraphQLList(type)
|
|
209
|
-
},
|
|
210
|
-
continuationToken: {
|
|
211
|
-
type: GraphQLString
|
|
212
|
-
}
|
|
213
|
-
},
|
|
214
|
-
name
|
|
215
|
-
}));
|
|
216
|
-
return this.modelsMap[name];
|
|
217
|
-
}
|
|
218
|
-
/**
|
|
219
|
-
*
|
|
220
|
-
* @param schema
|
|
221
|
-
* @param defaultName
|
|
222
|
-
* @param webdaGraph
|
|
223
|
-
* @returns
|
|
224
|
-
*/
|
|
225
|
-
getGraphQLFieldsFromSchema(schema, defaultName, webdaGraph, input) {
|
|
226
|
-
const fields = {};
|
|
227
|
-
const skipFields = [];
|
|
228
|
-
const attributeFilter = (skip = false) => info => {
|
|
229
|
-
if (!this.modelsMap[info.model] && skip) {
|
|
230
|
-
skipFields.push(info.attribute);
|
|
231
|
-
this.log("INFO", `Skipping field ${info.attribute} as model ${info.model} is not exposed`);
|
|
232
|
-
}
|
|
233
|
-
return this.modelsMap[info.model];
|
|
234
|
-
};
|
|
235
|
-
if (webdaGraph && !input) {
|
|
236
|
-
// In graphql parent should not be displayed
|
|
237
|
-
if (webdaGraph.parent) {
|
|
238
|
-
fields[webdaGraph.parent.attribute] = {
|
|
239
|
-
type: this.modelsMap[webdaGraph.parent.model],
|
|
240
|
-
resolve: async (source, _args, context, info) => {
|
|
241
|
-
this.countOperation(context);
|
|
242
|
-
// Check permission and model exists
|
|
243
|
-
return this.loadModelInstance(source[webdaGraph.parent.attribute] || "", this.app.getModel(webdaGraph.parent.model), context, info.fieldNodes?.find(node => node.name.value === webdaGraph.parent.attribute));
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
// Links should probably not be displayed
|
|
248
|
-
(webdaGraph.links || []).filter(attributeFilter()).forEach(link => {
|
|
249
|
-
if (link.type === "LINK") {
|
|
250
|
-
fields[link.attribute] = {
|
|
251
|
-
type: this.modelsMap[link.model],
|
|
252
|
-
resolve: async (source, _, context, info) => {
|
|
253
|
-
// Check permission and model exists
|
|
254
|
-
return this.loadModelInstance(source[link.attribute] || "", this.app.getModel(link.model), context, info.fieldNodes.find(node => node.name.value === link.attribute));
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
fields[link.attribute] = {
|
|
260
|
-
type: new GraphQLList(this.modelsMap[link.model]),
|
|
261
|
-
args: {
|
|
262
|
-
filter: {
|
|
263
|
-
type: GraphQLString
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
resolve: async (source, args, context, info) => {
|
|
267
|
-
const src = link.type === "LINKS_MAP" ? Object.values(source[link.attribute]) : source[link.attribute];
|
|
268
|
-
return (await Promise.all(src.map(i => this.loadModelInstance(i, this.getWebda().getModel(link.model), context, info.fieldNodes.find(node => node.name.value === link.attribute), args.filter ? new WebdaQL.PartialValidator(WebdaQL.unsanitize(args.filter)) : undefined)))).filter(i => i !== null);
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
(webdaGraph.maps || []).filter(attributeFilter()).forEach(map => {
|
|
274
|
-
fields[map.attribute] = {
|
|
275
|
-
type: new GraphQLList(this.modelsMap[map.model]),
|
|
276
|
-
args: {
|
|
277
|
-
filter: {
|
|
278
|
-
type: GraphQLString
|
|
279
|
-
}
|
|
280
|
-
},
|
|
281
|
-
resolve: async (source, args, context, info) => {
|
|
282
|
-
const modelDefinition = this.app.getModel(map.model);
|
|
283
|
-
return (await Promise.all(source[map.attribute].map(i => this.loadModelInstance(i, modelDefinition, context, info.fieldNodes.find(node => node.name.value === map.attribute), args.filter ? new WebdaQL.PartialValidator(WebdaQL.unsanitize(args.filter)) : undefined)))).filter(i => i !== null);
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
});
|
|
287
|
-
(webdaGraph.queries || []).filter(attributeFilter(true)).forEach(query => {
|
|
288
|
-
fields[query.attribute] = {
|
|
289
|
-
type: this.getGraphQLQueryResult(this.modelsMap[query.model]),
|
|
290
|
-
args: {
|
|
291
|
-
query: {
|
|
292
|
-
type: GraphQLString
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
resolve: async (source, args, context, info) => {
|
|
296
|
-
const res = await source[query.attribute].query(WebdaQL.unsanitize(args.query || ""), context);
|
|
297
|
-
this.countOperation(context, res.results.length);
|
|
298
|
-
return res;
|
|
299
|
-
}
|
|
300
|
-
};
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
for (const i in schema.properties) {
|
|
304
|
-
// Was initiated by the known graph
|
|
305
|
-
if (fields[i] || skipFields.includes(i) || (schema.properties[i].readOnly && input)) {
|
|
306
|
-
continue;
|
|
307
|
-
}
|
|
308
|
-
const prop = this.getGraphQLSchemaFromSchema(this.getJsonSchemaDefinition(schema.properties[i], schema.definitions), `${defaultName}_${i}`, input);
|
|
309
|
-
if (!prop) {
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
fields[i] = prop;
|
|
313
|
-
}
|
|
314
|
-
return fields;
|
|
315
|
-
}
|
|
316
|
-
/**
|
|
317
|
-
* Load model with filter on attributes
|
|
318
|
-
* @param knownFieldsOrId
|
|
319
|
-
* @param model
|
|
320
|
-
* @param context
|
|
321
|
-
* @param info
|
|
322
|
-
* @returns
|
|
323
|
-
*/
|
|
324
|
-
async loadModelInstance(knownFieldsOrId, model, context, info, filter) {
|
|
325
|
-
const res = typeof knownFieldsOrId === "string" ? { uuid: knownFieldsOrId } : knownFieldsOrId;
|
|
326
|
-
if (filter && !filter.eval(res)) {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
let operation =
|
|
330
|
-
// Check if we have a valid selection set
|
|
331
|
-
info === undefined ||
|
|
332
|
-
info.selectionSet === undefined ||
|
|
333
|
-
info.selectionSet.selections === undefined ||
|
|
334
|
-
info.selectionSet.selections.filter(node => node.kind === "Field").length === 0;
|
|
335
|
-
// Check if know all fields already
|
|
336
|
-
for (const field of (info?.selectionSet?.selections || []).filter(node => node.kind === "Field")) {
|
|
337
|
-
if (res[field.name.value] === undefined) {
|
|
338
|
-
operation = true;
|
|
339
|
-
break;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// We already know the whole answer so no more request needed
|
|
343
|
-
if (!operation && (!filter || !filter.wasPartialMatch())) {
|
|
344
|
-
return res;
|
|
345
|
-
}
|
|
346
|
-
// Count the operation then and retrieve the model
|
|
347
|
-
this.countOperation(context);
|
|
348
|
-
const modelInstance = await model.ref(res.uuid || "").get();
|
|
349
|
-
if (!modelInstance) {
|
|
350
|
-
throw new GraphQLError("Object not found", {
|
|
351
|
-
extensions: {
|
|
352
|
-
code: "NOT_FOUND"
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
if ((await modelInstance?.canAct(context, "get")) !== true) {
|
|
357
|
-
throw new GraphQLError("Permission denied", {
|
|
358
|
-
extensions: {
|
|
359
|
-
code: "PERMISSION_DENIED"
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
if (filter && !filter.eval(modelInstance, false)) {
|
|
364
|
-
return null;
|
|
365
|
-
}
|
|
366
|
-
return modelInstance;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Count the number of operations done with graphql
|
|
370
|
-
* @param context
|
|
371
|
-
*/
|
|
372
|
-
countOperation(context, increment = 1) {
|
|
373
|
-
const ext = context.getExtension("graphql");
|
|
374
|
-
if (ext?.count === this.parameters.maxOperationsPerRequest) {
|
|
375
|
-
throw new GraphQLError("Too many operations", {
|
|
376
|
-
extensions: {
|
|
377
|
-
code: "TOO_MANY_OPERATIONS"
|
|
378
|
-
}
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
if (ext) {
|
|
382
|
-
ext.count ?? (ext.count = 0);
|
|
383
|
-
ext.count += increment;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
resolve() {
|
|
387
|
-
super.resolve();
|
|
388
|
-
// Set-up ws server
|
|
389
|
-
this.wss = new WebSocketServer({ noServer: true });
|
|
390
|
-
this.getWebda().on("Webda.Init.Http", (http) => {
|
|
391
|
-
http.on("upgrade", (req, socket, head) => {
|
|
392
|
-
if (req.url === this.parameters.url) {
|
|
393
|
-
(async () => {
|
|
394
|
-
req.webdaContext ?? (req.webdaContext = await Core.get().getContextFromRequest(req));
|
|
395
|
-
await req.webdaContext.init();
|
|
396
|
-
this.wss.handleUpgrade(req, socket, head, ws => {
|
|
397
|
-
this.wss.emit("connection", ws, req);
|
|
398
|
-
});
|
|
399
|
-
})();
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
});
|
|
403
|
-
return this;
|
|
404
|
-
}
|
|
405
|
-
/**
|
|
406
|
-
* Generate GraphQL schema
|
|
407
|
-
*/
|
|
408
|
-
generateSchema() {
|
|
409
|
-
const rootFields = {};
|
|
410
|
-
const mutations = {};
|
|
411
|
-
const subscriptions = {};
|
|
412
|
-
const models = this.app.getModels();
|
|
413
|
-
const graph = this.app.getGraph();
|
|
414
|
-
this.modelsMap = {};
|
|
415
|
-
for (const i in models) {
|
|
416
|
-
const model = models[i];
|
|
417
|
-
// Not exposed
|
|
418
|
-
if (!model.Expose || !this.parameters.isIncluded(model.getIdentifier())) {
|
|
419
|
-
continue;
|
|
420
|
-
}
|
|
421
|
-
const schema = model.getSchema();
|
|
422
|
-
if (!schema) {
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
const name = this.app.getShortId(i).replace("/", "_");
|
|
426
|
-
this.log("INFO", "Add GraphQL type", name);
|
|
427
|
-
this.modelsMap[i] = new GraphQLObjectType({
|
|
428
|
-
fields: () => this.getGraphQLFieldsFromSchema(schema, name, graph[i]),
|
|
429
|
-
name
|
|
430
|
-
});
|
|
431
|
-
const input = new GraphQLInputObjectType({
|
|
432
|
-
fields: this.getGraphQLFieldsFromSchema(schema, name + "Input", graph[i], true),
|
|
433
|
-
name: name + "Input"
|
|
434
|
-
});
|
|
435
|
-
if (!model.Expose.restrict.create) {
|
|
436
|
-
mutations[`create${name}`] = {
|
|
437
|
-
type: this.modelsMap[i],
|
|
438
|
-
args: {
|
|
439
|
-
[name]: { type: input }
|
|
440
|
-
},
|
|
441
|
-
resolve: async (_, args, context) => {
|
|
442
|
-
const object = new model().load(args[name]);
|
|
443
|
-
this.log("INFO", "Create", object, context.getCurrentUserId());
|
|
444
|
-
if ((await object?.canAct(context, "create")) !== true) {
|
|
445
|
-
throw new GraphQLError("Permission denied", {
|
|
446
|
-
extensions: {
|
|
447
|
-
code: "PERMISSION_DENIED"
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
await object.save();
|
|
452
|
-
return object;
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
if (!model.Expose.restrict.update) {
|
|
457
|
-
mutations[`update${name}`] = {
|
|
458
|
-
type: this.modelsMap[i],
|
|
459
|
-
args: {
|
|
460
|
-
[name]: { type: input },
|
|
461
|
-
uuid: {
|
|
462
|
-
type: GraphQLString
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
resolve: async (_, args, context) => {
|
|
466
|
-
const object = await model.ref(args.uuid).get();
|
|
467
|
-
if ((await object?.canAct(context, "update")) !== true) {
|
|
468
|
-
throw new GraphQLError("Permission denied", {
|
|
469
|
-
extensions: {
|
|
470
|
-
code: "PERMISSION_DENIED"
|
|
471
|
-
}
|
|
472
|
-
});
|
|
473
|
-
}
|
|
474
|
-
return object.load(args[name]).save();
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
// check for actions
|
|
479
|
-
if (!model.Expose.restrict.delete) {
|
|
480
|
-
mutations[`delete${name}`] = {
|
|
481
|
-
type: new GraphQLObjectType({ name: `delete${name}`, fields: { success: { type: GraphQLBoolean } } }),
|
|
482
|
-
args: {
|
|
483
|
-
uuid: {
|
|
484
|
-
type: GraphQLString
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
resolve: async (_, args, context) => {
|
|
488
|
-
const object = await model.ref(args.uuid).get();
|
|
489
|
-
if ((await object?.canAct(context, "delete")) !== true) {
|
|
490
|
-
throw new GraphQLError("Permission denied", {
|
|
491
|
-
extensions: {
|
|
492
|
-
code: "PERMISSION_DENIED"
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
await object.delete();
|
|
497
|
-
return {
|
|
498
|
-
success: true
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
// Retrieval
|
|
504
|
-
const plural = this.transformName(this.app.getModelPlural(i).split("/").pop());
|
|
505
|
-
rootFields[plural] = {
|
|
506
|
-
type: this.getGraphQLQueryResult(this.modelsMap[i]),
|
|
507
|
-
args: {
|
|
508
|
-
query: {
|
|
509
|
-
type: GraphQLString,
|
|
510
|
-
defaultValue: ""
|
|
511
|
-
}
|
|
512
|
-
},
|
|
513
|
-
resolve: async (_, args, context) => {
|
|
514
|
-
return await model.query(WebdaQL.unsanitize(args.query || ""), true, context);
|
|
515
|
-
},
|
|
516
|
-
subscribe: async (_source, args, context) => {
|
|
517
|
-
this.log("DEBUG", "Subscription called on", args);
|
|
518
|
-
return this.registerAsyncIteratorQuery(model, plural, WebdaQL.unsanitize(args.query || ""), context);
|
|
519
|
-
}
|
|
520
|
-
};
|
|
521
|
-
/**
|
|
522
|
-
* Subscription for a specific object
|
|
523
|
-
*/
|
|
524
|
-
subscriptions[this.transformName(i.split("/").pop())] = {
|
|
525
|
-
type: this.modelsMap[i],
|
|
526
|
-
args: {
|
|
527
|
-
[model.getUuidField()]: {
|
|
528
|
-
type: GraphQLString
|
|
529
|
-
}
|
|
530
|
-
},
|
|
531
|
-
subscribe: async (_source, args, context) => {
|
|
532
|
-
this.log("DEBUG", "Subscription called on", args);
|
|
533
|
-
return this.registerAsyncIterator(model, args[model.getUuidField()], context);
|
|
534
|
-
}
|
|
535
|
-
};
|
|
536
|
-
const events = model.getClientEvents();
|
|
537
|
-
const modelEvents = events
|
|
538
|
-
.filter(e => typeof e !== "string" && e.global)
|
|
539
|
-
.map((e) => e.name);
|
|
540
|
-
const instanceEvents = events
|
|
541
|
-
.filter(e => typeof e === "string" || !e.global)
|
|
542
|
-
.map(e => (typeof e === "string" ? e : e.name));
|
|
543
|
-
if (modelEvents.length) {
|
|
544
|
-
const eventTypes = {};
|
|
545
|
-
modelEvents.forEach(e => {
|
|
546
|
-
eventTypes[e] = {
|
|
547
|
-
type: AnyScalarType
|
|
548
|
-
};
|
|
549
|
-
});
|
|
550
|
-
eventTypes["latestEventTime"] = { type: GraphQLLong };
|
|
551
|
-
/**
|
|
552
|
-
* Subscription for all events on a class of objects
|
|
553
|
-
*/
|
|
554
|
-
subscriptions[plural + "Events"] = {
|
|
555
|
-
type: new GraphQLObjectType({ fields: eventTypes, name: plural + "Events" }),
|
|
556
|
-
subscribe: async (_source, args, context, info) => {
|
|
557
|
-
const subscribedEvents = this.getSubscribedEvents(info);
|
|
558
|
-
this.log("DEBUG", "Subscription called on", model, args[model.getUuidField()], subscribedEvents);
|
|
559
|
-
return this.registerAsyncEventIterator(model, null, subscribedEvents, context, plural + "Events");
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
if (instanceEvents.length) {
|
|
564
|
-
const eventTypes = {};
|
|
565
|
-
instanceEvents.forEach(e => {
|
|
566
|
-
eventTypes[e] = { type: AnyScalarType };
|
|
567
|
-
});
|
|
568
|
-
eventTypes["latestEventTime"] = { type: GraphQLLong };
|
|
569
|
-
/**
|
|
570
|
-
* Subscription for event on a specific object
|
|
571
|
-
*/
|
|
572
|
-
subscriptions[this.transformName(i.split("/").pop()) + "Events"] = {
|
|
573
|
-
type: new GraphQLObjectType({ fields: eventTypes, name: this.transformName(i.split("/").pop()) + "Events2" }),
|
|
574
|
-
args: {
|
|
575
|
-
[model.getUuidField()]: {
|
|
576
|
-
type: GraphQLString
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
subscribe: async (_source, args, context, info) => {
|
|
580
|
-
const subscribedEvents = this.getSubscribedEvents(info);
|
|
581
|
-
this.log("DEBUG", "Subscription called on", model, args[model.getUuidField()], subscribedEvents);
|
|
582
|
-
return this.registerAsyncEventIterator(model, args[model.getUuidField()], subscribedEvents, context, this.transformName(i.split("/").pop()) + "Events");
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Query for a specific object
|
|
588
|
-
*/
|
|
589
|
-
rootFields[this.transformName(i.split("/").pop())] = {
|
|
590
|
-
type: this.modelsMap[i],
|
|
591
|
-
args: {
|
|
592
|
-
[model.getUuidField()]: {
|
|
593
|
-
type: GraphQLString
|
|
594
|
-
}
|
|
595
|
-
},
|
|
596
|
-
resolve: async (_source, args, context, _info) => {
|
|
597
|
-
this.countOperation(context);
|
|
598
|
-
return this.loadModelInstance(args[model.getUuidField()] || "", model, context);
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
// Expose the current user under me
|
|
603
|
-
if (this.parameters.exposeMe) {
|
|
604
|
-
const userGraph = this.app.completeNamespace(this.parameters.userModel);
|
|
605
|
-
const model = this.app.getModel(this.parameters.userModel);
|
|
606
|
-
if (this.modelsMap[userGraph] && model.Expose && model.Expose.restrict.get !== true) {
|
|
607
|
-
rootFields[this.transformName("Me")] = {
|
|
608
|
-
type: this.modelsMap[userGraph],
|
|
609
|
-
resolve: async (_, _args, context) => {
|
|
610
|
-
this.countOperation(context);
|
|
611
|
-
return context.getCurrentUser();
|
|
612
|
-
}
|
|
613
|
-
};
|
|
614
|
-
subscriptions["Me"] = {
|
|
615
|
-
type: this.modelsMap[userGraph],
|
|
616
|
-
subscribe: async (_source, _args, context) => {
|
|
617
|
-
if (!context.getCurrentUserId()) {
|
|
618
|
-
throw new GraphQLError("Permission denied", {
|
|
619
|
-
extensions: {
|
|
620
|
-
code: "PERMISSION_DENIED"
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
const user = await context.getCurrentUser();
|
|
625
|
-
return this.registerAsyncIterator(user.__class, context.getCurrentUserId(), context, "Me");
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
const events = model.getClientEvents();
|
|
629
|
-
const instanceEvents = events
|
|
630
|
-
.filter(e => typeof e === "string" || !e.global)
|
|
631
|
-
.map(e => (typeof e === "string" ? e : e.name));
|
|
632
|
-
if (instanceEvents.length) {
|
|
633
|
-
const eventTypes = {};
|
|
634
|
-
instanceEvents.forEach(e => {
|
|
635
|
-
eventTypes[e] = { type: AnyScalarType };
|
|
636
|
-
});
|
|
637
|
-
subscriptions["MeEvents"] = {
|
|
638
|
-
type: new GraphQLObjectType({ fields: eventTypes, name: "MeEvents" }),
|
|
639
|
-
subscribe: async (_source, args, context, info) => {
|
|
640
|
-
if (!context.getCurrentUserId()) {
|
|
641
|
-
throw new GraphQLError("Permission denied", {
|
|
642
|
-
extensions: {
|
|
643
|
-
code: "PERMISSION_DENIED"
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
}
|
|
647
|
-
const subscribedEvents = (info.fieldNodes[0].selectionSet?.selections || [])
|
|
648
|
-
.filter(node => node.kind === "Field")
|
|
649
|
-
.map(n => n.name.value);
|
|
650
|
-
const user = await context.getCurrentUser();
|
|
651
|
-
return this.registerAsyncEventIterator(user.__class, context.getCurrentUserId(), subscribedEvents, context, "MeEvents");
|
|
652
|
-
}
|
|
653
|
-
};
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
this.log("WARN", "Cannot expose me, user model is not exposed or get is restricted or type is not unavailable");
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
const services = this.getWebda().getServices();
|
|
661
|
-
for (const i in services) {
|
|
662
|
-
if (services[i]?.getClientEvents === undefined) {
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
const events = services[i]?.getClientEvents() || [];
|
|
666
|
-
if (events.length === 0) {
|
|
667
|
-
continue;
|
|
668
|
-
}
|
|
669
|
-
const fields = {};
|
|
670
|
-
events.forEach(e => {
|
|
671
|
-
fields[e] = { type: AnyScalarType };
|
|
672
|
-
});
|
|
673
|
-
const id = `${i}Events`;
|
|
674
|
-
subscriptions[id] = {
|
|
675
|
-
type: new GraphQLObjectType({
|
|
676
|
-
fields,
|
|
677
|
-
name: id
|
|
678
|
-
}),
|
|
679
|
-
subscribe: async (_source, args, context, info) => {
|
|
680
|
-
const fieldsMap = {};
|
|
681
|
-
(info.fieldNodes[0].selectionSet?.selections || [])
|
|
682
|
-
.filter(node => node.kind === "Field")
|
|
683
|
-
.map(n => n.name.value)
|
|
684
|
-
.forEach(n => (fieldsMap[n] = true));
|
|
685
|
-
return new EventIterator(services[i], fieldsMap, id, {}).iterate();
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
// Copy the rootFields without resolver
|
|
690
|
-
for (const i in rootFields) {
|
|
691
|
-
subscriptions[i] ?? (subscriptions[i] = { ...rootFields[i], resolve: undefined });
|
|
692
|
-
}
|
|
693
|
-
if (this.parameters.globalSubscription) {
|
|
694
|
-
// Create global type based on all previous subscriptions
|
|
695
|
-
subscriptions["Aggregate"] = {
|
|
696
|
-
type: new GraphQLObjectType({
|
|
697
|
-
fields: subscriptions,
|
|
698
|
-
name: "AggregateSubscriptions"
|
|
699
|
-
}),
|
|
700
|
-
subscribe: async (_source, args2, context, info) => {
|
|
701
|
-
const iterators = {};
|
|
702
|
-
const p = [];
|
|
703
|
-
// Check if know all fields already
|
|
704
|
-
for (const field of (info.fieldNodes[0].selectionSet?.selections || []).filter(node => node.kind === "Field")) {
|
|
705
|
-
const name = field.name.value;
|
|
706
|
-
const args = {};
|
|
707
|
-
field.arguments?.forEach(arg => {
|
|
708
|
-
// @ts-ignore
|
|
709
|
-
args[arg.name.value] = arg.value.value;
|
|
710
|
-
});
|
|
711
|
-
p.push((async () => {
|
|
712
|
-
iterators[name] = await subscriptions[name].subscribe(_source, args, context, {
|
|
713
|
-
fieldNodes: [field] // Abusing a bit the builder currently
|
|
714
|
-
});
|
|
715
|
-
})());
|
|
716
|
-
}
|
|
717
|
-
await Promise.all(p);
|
|
718
|
-
return MergedIterator.iterate({
|
|
719
|
-
Aggregate: MergedIterator.iterate(iterators, true, data => {
|
|
720
|
-
return Object.values(data).pop();
|
|
721
|
-
})
|
|
722
|
-
});
|
|
723
|
-
}
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
this.schema = this.getGraphQLSchema(rootFields, mutations, subscriptions);
|
|
727
|
-
}
|
|
728
|
-
/**
|
|
729
|
-
* Return the events to subscribe to
|
|
730
|
-
* @param info
|
|
731
|
-
* @returns
|
|
732
|
-
*/
|
|
733
|
-
getSubscribedEvents(info) {
|
|
734
|
-
return (info.fieldNodes[0].selectionSet?.selections || [])
|
|
735
|
-
.filter(node => node.kind === "Field" && node.name.value !== "latestEventTime")
|
|
736
|
-
.map(n => n.name.value);
|
|
737
|
-
}
|
|
738
|
-
/**
|
|
739
|
-
*
|
|
740
|
-
* @param model
|
|
741
|
-
* @param arg1
|
|
742
|
-
* @param context
|
|
743
|
-
* @returns
|
|
744
|
-
*/
|
|
745
|
-
async registerAsyncIteratorQuery(model, plural, query, context) {
|
|
746
|
-
let result = await model.query(query, true, context);
|
|
747
|
-
const queryInfo = new WebdaQL.QueryValidator(query);
|
|
748
|
-
const updatedCallback = async (evt) => {
|
|
749
|
-
this.log("INFO", "Event from", evt.emitterId, evt.object_id);
|
|
750
|
-
if (!result.results.find(e => evt.object_id === e.getUuid()))
|
|
751
|
-
return;
|
|
752
|
-
// We rely on the cache of the store to get the full object
|
|
753
|
-
// We let the other listeners finish before returning the object
|
|
754
|
-
await new Promise(resolve => nextTick(resolve));
|
|
755
|
-
return {
|
|
756
|
-
continuationToken: result.continuationToken,
|
|
757
|
-
results: await Promise.all(result.results.map(r => (r.getUuid() === evt.object_id ? model.ref(evt.object_id).get() : r)))
|
|
758
|
-
};
|
|
759
|
-
};
|
|
760
|
-
const events = {
|
|
761
|
-
"Store.Updated": updatedCallback,
|
|
762
|
-
// Deleted is different as we need to return null
|
|
763
|
-
"Store.Deleted": async (evt) => {
|
|
764
|
-
if (!result.results.find(e => evt.object_id === e.getUuid()))
|
|
765
|
-
return;
|
|
766
|
-
result = await model.query(query, true, context);
|
|
767
|
-
return result;
|
|
768
|
-
},
|
|
769
|
-
"Store.Saved": async (evt) => {
|
|
770
|
-
// If object match the query and is not in the result and can be read by the user
|
|
771
|
-
if (queryInfo.eval(evt.object) && !queryInfo.getOffset() && evt.object?.canAct(context, "get")) {
|
|
772
|
-
// Should check with the order by of the query to see if we need to recompute
|
|
773
|
-
result = await model.query(query, true, context);
|
|
774
|
-
return result;
|
|
775
|
-
}
|
|
776
|
-
return;
|
|
777
|
-
},
|
|
778
|
-
"Store.PatchUpdated": updatedCallback,
|
|
779
|
-
"Store.PartialUpdated": updatedCallback
|
|
780
|
-
};
|
|
781
|
-
return new EventIterator(model.store(), events, plural, result).iterate();
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
*
|
|
785
|
-
* @param model
|
|
786
|
-
* @param uuid
|
|
787
|
-
* @param context
|
|
788
|
-
* @returns
|
|
789
|
-
*/
|
|
790
|
-
async registerAsyncIterator(model, uuid, context, identifier) {
|
|
791
|
-
const updatedCallback = async (evt) => {
|
|
792
|
-
this.log("INFO", "Event from", evt.emitterId, evt.object_id);
|
|
793
|
-
if (evt.object_id !== uuid)
|
|
794
|
-
return;
|
|
795
|
-
// We rely on the cache of the store to get the full object
|
|
796
|
-
// We let the other listeners finish before returning the object
|
|
797
|
-
await new Promise(resolve => nextTick(resolve));
|
|
798
|
-
return model.ref(evt.object_id).get();
|
|
799
|
-
};
|
|
800
|
-
const events = {
|
|
801
|
-
"Store.Updated": updatedCallback,
|
|
802
|
-
// Deleted is different as we need to return null
|
|
803
|
-
"Store.Deleted": evt => {
|
|
804
|
-
if (evt.object_id !== uuid)
|
|
805
|
-
return;
|
|
806
|
-
return null;
|
|
807
|
-
},
|
|
808
|
-
"Store.PatchUpdated": updatedCallback,
|
|
809
|
-
"Store.PartialUpdated": updatedCallback
|
|
810
|
-
};
|
|
811
|
-
const modelInstance = await model.ref(uuid).get();
|
|
812
|
-
// Ensure we have the permission to get the object
|
|
813
|
-
if ((await modelInstance?.canAct(context, "get")) !== true) {
|
|
814
|
-
throw new GraphQLError("Permission denied", {
|
|
815
|
-
extensions: {
|
|
816
|
-
code: "PERMISSION_DENIED"
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
return new EventIterator(model.store(), events, identifier || model.getIdentifier(), modelInstance).iterate();
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
*
|
|
824
|
-
* @param model
|
|
825
|
-
* @param uuid
|
|
826
|
-
* @param context
|
|
827
|
-
* @returns
|
|
828
|
-
*/
|
|
829
|
-
async registerAsyncEventIterator(model, uuid, events, context, identifier) {
|
|
830
|
-
const updatedCallback = eventName => async (evt) => {
|
|
831
|
-
if (uuid === null || evt.object_id !== uuid)
|
|
832
|
-
return;
|
|
833
|
-
// We rely on the cache of the store to get the full object
|
|
834
|
-
// We let the other listeners finish before returning the object
|
|
835
|
-
return { latestEventTime: Date.now(), [eventName]: evt };
|
|
836
|
-
};
|
|
837
|
-
const eventsMap = {};
|
|
838
|
-
const modelInstance = uuid !== null ? await model.ref(uuid).get() : undefined;
|
|
839
|
-
events
|
|
840
|
-
.filter(e => model.authorizeClientEvent(e, context, modelInstance))
|
|
841
|
-
.forEach(e => (eventsMap[e] = updatedCallback(e)));
|
|
842
|
-
// Ensure we have the permission to listen to the object
|
|
843
|
-
if (Object.keys(eventsMap).length === 0) {
|
|
844
|
-
throw new GraphQLError("Permission denied", {
|
|
845
|
-
extensions: {
|
|
846
|
-
code: "PERMISSION_DENIED"
|
|
847
|
-
}
|
|
848
|
-
});
|
|
849
|
-
}
|
|
850
|
-
return new EventIterator(model.store(), eventsMap, identifier, { logout: { evt: "nok?" } }).iterate();
|
|
851
|
-
}
|
|
852
|
-
/**
|
|
853
|
-
*
|
|
854
|
-
* @param rootFields
|
|
855
|
-
* @param mutation
|
|
856
|
-
* @returns
|
|
857
|
-
*/
|
|
858
|
-
getGraphQLSchema(rootFields, mutations = {}, subscription = {}) {
|
|
859
|
-
// Emit Webda.GraphQL.Schema to allow other services to contribute
|
|
860
|
-
this.emit("Webda.GraphQL.Schema", {
|
|
861
|
-
rootFields,
|
|
862
|
-
mutations,
|
|
863
|
-
subscription
|
|
864
|
-
});
|
|
865
|
-
return new GraphQLSchema({
|
|
866
|
-
query: new GraphQLObjectType({
|
|
867
|
-
name: "Query",
|
|
868
|
-
fields: rootFields
|
|
869
|
-
}),
|
|
870
|
-
types: Object.values(this.modelsMap),
|
|
871
|
-
subscription: Object.keys({ ...subscription }).length > 0
|
|
872
|
-
? new GraphQLObjectType({ fields: { ...subscription }, name: "Subscription" })
|
|
873
|
-
: null,
|
|
874
|
-
mutation: Object.keys(mutations).length > 0 ? new GraphQLObjectType({ fields: mutations, name: "Mutations" }) : null
|
|
875
|
-
});
|
|
876
|
-
}
|
|
877
|
-
/**
|
|
878
|
-
* @override
|
|
879
|
-
*/
|
|
880
|
-
async init() {
|
|
881
|
-
var _a;
|
|
882
|
-
await super.init();
|
|
883
|
-
// If not define then fallback to debug mode
|
|
884
|
-
(_a = this.parameters).exposeGraphiQL ?? (_a.exposeGraphiQL = this.getWebda().isDebug());
|
|
885
|
-
this.app = this.getWebda().getApplication();
|
|
886
|
-
this.generateSchema();
|
|
887
|
-
// Generate GraphQL schema
|
|
888
|
-
this.handler = createHandler({
|
|
889
|
-
schema: this.schema,
|
|
890
|
-
context: (req, _) => {
|
|
891
|
-
return req.context;
|
|
892
|
-
}
|
|
893
|
-
});
|
|
894
|
-
// Define the mechanism to handle the websocket
|
|
895
|
-
this.wsHandler = makeServer({
|
|
896
|
-
schema: this.schema,
|
|
897
|
-
context: async (req) => {
|
|
898
|
-
// @ts-ignore
|
|
899
|
-
return req.extra?.context || {};
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
this.wss.on("connection", async (socket, request) => {
|
|
903
|
-
// a new socket opened, let graphql-ws take over
|
|
904
|
-
const closed = this.wsHandler.opened({
|
|
905
|
-
protocol: socket.protocol, // will be validated
|
|
906
|
-
send: data => new Promise((resolve, reject) => {
|
|
907
|
-
socket.send(data, err => (err ? reject(err) : resolve()));
|
|
908
|
-
}), // control your data flow by timing the promise resolve
|
|
909
|
-
close: (code, reason) => socket.close(code, reason), // there are protocol standard closures
|
|
910
|
-
onMessage: cb => socket.on("message", async (event) => {
|
|
911
|
-
try {
|
|
912
|
-
// wait for the the operation to complete
|
|
913
|
-
// - if init message, waits for connect
|
|
914
|
-
// - if query/mutation, waits for result
|
|
915
|
-
// - if subscription, waits for complete
|
|
916
|
-
await cb(event.toString());
|
|
917
|
-
}
|
|
918
|
-
catch (err) {
|
|
919
|
-
// all errors that could be thrown during the
|
|
920
|
-
// execution of operations will be caught here
|
|
921
|
-
socket.close(CloseCode.InternalServerError, err.message);
|
|
922
|
-
}
|
|
923
|
-
})
|
|
924
|
-
},
|
|
925
|
-
// pass values to the `extra` field in the context
|
|
926
|
-
{ socket, request, context: request.webdaContext });
|
|
927
|
-
this.log("INFO", "Socket once closed");
|
|
928
|
-
// notify server that the socket closed
|
|
929
|
-
socket.once("close", (code, reason) => {
|
|
930
|
-
this.log("INFO", "Socket closed - clean up Iterators");
|
|
931
|
-
closed(code, reason.toString());
|
|
932
|
-
});
|
|
933
|
-
});
|
|
934
|
-
return this;
|
|
935
|
-
}
|
|
936
|
-
handleModel(model, name, context) {
|
|
937
|
-
//throw new Error("Method not implemented.");
|
|
938
|
-
return true;
|
|
939
|
-
}
|
|
940
|
-
/**
|
|
941
|
-
* Serve schema and graphqli
|
|
942
|
-
* @params ctx
|
|
943
|
-
*/
|
|
944
|
-
async schemaRoute(ctx) {
|
|
945
|
-
const httpContext = ctx.getHttpContext();
|
|
946
|
-
if (!this.parameters.exposeGraphiQL) {
|
|
947
|
-
throw new WebdaError.NotFound("GraphiQL not exposed");
|
|
948
|
-
}
|
|
949
|
-
if (httpContext.getHeader("X-GraphiQL-Schema") === "true") {
|
|
950
|
-
ctx.writeHead(200, { "Content-Type": "application/graphql" });
|
|
951
|
-
ctx.write(printSchema(this.schema));
|
|
952
|
-
}
|
|
953
|
-
else {
|
|
954
|
-
ctx.writeHead(200, { "Content-Type": "text/html" });
|
|
955
|
-
ctx.write(GraphIQL.replace("{{URL}}", httpContext.getAbsoluteUrl()).replace("{{WSURL}}", httpContext.getAbsoluteUrl().replace("http", "ws")));
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Endpoint for the GraphQL schema
|
|
960
|
-
* @param ctx
|
|
961
|
-
* @returns
|
|
962
|
-
*/
|
|
963
|
-
async endpoint(ctx) {
|
|
964
|
-
const httpContext = ctx.getHttpContext();
|
|
965
|
-
ctx.setExtension("graphql", { count: 0 });
|
|
966
|
-
const [body, init] = await this.handler({
|
|
967
|
-
url: httpContext.getUrl(),
|
|
968
|
-
method: httpContext.getMethod(),
|
|
969
|
-
headers: httpContext.getHeaders(),
|
|
970
|
-
body: await ctx.getInput(),
|
|
971
|
-
raw: ctx,
|
|
972
|
-
context: ctx
|
|
973
|
-
});
|
|
974
|
-
ctx.writeHead(init.status, init.headers);
|
|
975
|
-
ctx.write(body);
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
__decorate([
|
|
979
|
-
Route(".", ["GET"], { hidden: true })
|
|
980
|
-
], GraphQLService.prototype, "schemaRoute", null);
|
|
981
|
-
__decorate([
|
|
982
|
-
Route(".", ["POST"], {
|
|
983
|
-
post: {
|
|
984
|
-
summary: "GraphQL endpoint",
|
|
985
|
-
requestBody: {
|
|
986
|
-
content: {
|
|
987
|
-
"application/json": {
|
|
988
|
-
schema: {
|
|
989
|
-
type: "object",
|
|
990
|
-
properties: {
|
|
991
|
-
query: {
|
|
992
|
-
type: "string"
|
|
993
|
-
},
|
|
994
|
-
variables: {
|
|
995
|
-
type: "object"
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
}
|
|
1003
|
-
})
|
|
1004
|
-
], GraphQLService.prototype, "endpoint", null);
|
|
1005
|
-
//# sourceMappingURL=graphql.js.map
|