sofa-api 0.5.0 → 0.7.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/{dist/ast.d.ts → ast.d.ts} +7 -7
- package/{dist/common.d.ts → common.d.ts} +2 -1
- package/express.d.ts +5 -0
- package/index.cjs.js +1058 -0
- package/{dist/index.d.ts → index.d.ts} +6 -6
- package/index.esm.js +1050 -0
- package/{dist/logger.d.ts → logger.d.ts} +1 -1
- package/{dist/open-api → open-api}/index.d.ts +14 -12
- package/{dist/open-api → open-api}/interfaces.d.ts +325 -325
- package/{dist/open-api → open-api}/operations.d.ts +7 -7
- package/{dist/open-api → open-api}/types.d.ts +3 -3
- package/{dist/open-api → open-api}/utils.d.ts +2 -2
- package/{dist/operation.d.ts → operation.d.ts} +12 -12
- package/package.json +23 -71
- package/{dist/parse.d.ts → parse.d.ts} +6 -6
- package/{dist/sofa.d.ts → sofa.d.ts} +33 -33
- package/{dist/subscriptions.d.ts → subscriptions.d.ts} +38 -38
- package/{dist/types.d.ts → types.d.ts} +17 -17
- package/.DS_Store +0 -0
- package/.git/logs/refs/remotes/origin/changelog +0 -1
- package/.git/refs/remotes/origin/changelog +0 -1
- package/CHANGELOG.md +0 -30
- package/LICENSE +0 -21
- package/README.md +0 -276
- package/dist/ast.js +0 -16
- package/dist/ast.js.map +0 -1
- package/dist/common.js +0 -8
- package/dist/common.js.map +0 -1
- package/dist/express.d.ts +0 -5
- package/dist/express.js +0 -226
- package/dist/express.js.map +0 -1
- package/dist/index.js +0 -13
- package/dist/index.js.map +0 -1
- package/dist/logger.js +0 -8
- package/dist/logger.js.map +0 -1
- package/dist/open-api/index.js +0 -67
- package/dist/open-api/index.js.map +0 -1
- package/dist/open-api/interfaces.js +0 -3
- package/dist/open-api/interfaces.js.map +0 -1
- package/dist/open-api/operations.js +0 -112
- package/dist/open-api/operations.js.map +0 -1
- package/dist/open-api/types.js +0 -50
- package/dist/open-api/types.js.map +0 -1
- package/dist/open-api/utils.js +0 -29
- package/dist/open-api/utils.js.map +0 -1
- package/dist/operation.js +0 -258
- package/dist/operation.js.map +0 -1
- package/dist/parse.js +0 -46
- package/dist/parse.js.map +0 -1
- package/dist/sofa.js +0 -90
- package/dist/sofa.js.map +0 -1
- package/dist/subscriptions.js +0 -174
- package/dist/subscriptions.js.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
package/index.esm.js
ADDED
|
@@ -0,0 +1,1050 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import { Kind, isListType, isNonNullType, getNamedType, isScalarType, isUnionType, isInterfaceType, isObjectType, getOperationAST, graphql, isEqualType, GraphQLBoolean, isInputObjectType, subscribe, print, parse, printType, isIntrospectionType } from 'graphql';
|
|
4
|
+
import { camelCase, paramCase } from 'change-case';
|
|
5
|
+
import { createLogger, transports, format } from 'winston';
|
|
6
|
+
import { v4 } from 'uuid';
|
|
7
|
+
import axios from 'axios';
|
|
8
|
+
import { isAsyncIterable, forAwaitEach } from 'iterall';
|
|
9
|
+
import { stringify } from 'yamljs';
|
|
10
|
+
import { writeFileSync } from 'fs';
|
|
11
|
+
import { titleCase } from 'title-case';
|
|
12
|
+
|
|
13
|
+
const logger = createLogger({
|
|
14
|
+
transports: [new transports.Console()],
|
|
15
|
+
format: format.combine(format.colorize(), format.simple()),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
let operationVariables = [];
|
|
19
|
+
function addOperationVariable(variable) {
|
|
20
|
+
operationVariables.push(variable);
|
|
21
|
+
}
|
|
22
|
+
function resetOperationVariables() {
|
|
23
|
+
operationVariables = [];
|
|
24
|
+
}
|
|
25
|
+
function buildOperationName(name) {
|
|
26
|
+
return camelCase(name);
|
|
27
|
+
}
|
|
28
|
+
function buildOperation({ schema, kind, field, models, ignore, depthLimit, }) {
|
|
29
|
+
resetOperationVariables();
|
|
30
|
+
logger.debug(`[Sofa] Building ${field} ${kind}`);
|
|
31
|
+
const document = buildDocumentNode({
|
|
32
|
+
schema,
|
|
33
|
+
fieldName: field,
|
|
34
|
+
kind,
|
|
35
|
+
models,
|
|
36
|
+
ignore,
|
|
37
|
+
depthLimit: depthLimit || 1,
|
|
38
|
+
});
|
|
39
|
+
// attach variables
|
|
40
|
+
document.definitions[0].variableDefinitions = [
|
|
41
|
+
...operationVariables,
|
|
42
|
+
];
|
|
43
|
+
resetOperationVariables();
|
|
44
|
+
return document;
|
|
45
|
+
}
|
|
46
|
+
function buildDocumentNode({ schema, fieldName, kind, models, ignore, depthLimit, }) {
|
|
47
|
+
const typeMap = {
|
|
48
|
+
query: schema.getQueryType(),
|
|
49
|
+
mutation: schema.getMutationType(),
|
|
50
|
+
subscription: schema.getSubscriptionType(),
|
|
51
|
+
};
|
|
52
|
+
const type = typeMap[kind];
|
|
53
|
+
const field = type.getFields()[fieldName];
|
|
54
|
+
const operationName = buildOperationName(`${fieldName}_${kind}`);
|
|
55
|
+
if (field.args) {
|
|
56
|
+
field.args.forEach(arg => {
|
|
57
|
+
addOperationVariable(resolveVariable(arg));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const operationNode = {
|
|
61
|
+
kind: Kind.OPERATION_DEFINITION,
|
|
62
|
+
operation: kind,
|
|
63
|
+
name: {
|
|
64
|
+
kind: 'Name',
|
|
65
|
+
value: operationName,
|
|
66
|
+
},
|
|
67
|
+
variableDefinitions: [],
|
|
68
|
+
selectionSet: {
|
|
69
|
+
kind: Kind.SELECTION_SET,
|
|
70
|
+
selections: [
|
|
71
|
+
resolveField({
|
|
72
|
+
type,
|
|
73
|
+
field,
|
|
74
|
+
models,
|
|
75
|
+
firstCall: true,
|
|
76
|
+
path: [],
|
|
77
|
+
ancestors: [],
|
|
78
|
+
ignore,
|
|
79
|
+
depthLimit,
|
|
80
|
+
schema,
|
|
81
|
+
}),
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const document = {
|
|
86
|
+
kind: Kind.DOCUMENT,
|
|
87
|
+
definitions: [operationNode],
|
|
88
|
+
};
|
|
89
|
+
return document;
|
|
90
|
+
}
|
|
91
|
+
function resolveSelectionSet({ parent, type, models, firstCall, path, ancestors, ignore, depthLimit, schema, }) {
|
|
92
|
+
if (isUnionType(type)) {
|
|
93
|
+
const types = type.getTypes();
|
|
94
|
+
return {
|
|
95
|
+
kind: Kind.SELECTION_SET,
|
|
96
|
+
selections: types
|
|
97
|
+
.filter(t => !hasCircularRef([...ancestors, t], {
|
|
98
|
+
depth: depthLimit,
|
|
99
|
+
}))
|
|
100
|
+
.map(t => {
|
|
101
|
+
return {
|
|
102
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
103
|
+
typeCondition: {
|
|
104
|
+
kind: Kind.NAMED_TYPE,
|
|
105
|
+
name: {
|
|
106
|
+
kind: Kind.NAME,
|
|
107
|
+
value: t.name,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
selectionSet: resolveSelectionSet({
|
|
111
|
+
parent: type,
|
|
112
|
+
type: t,
|
|
113
|
+
models,
|
|
114
|
+
path,
|
|
115
|
+
ancestors,
|
|
116
|
+
ignore,
|
|
117
|
+
depthLimit,
|
|
118
|
+
schema,
|
|
119
|
+
}),
|
|
120
|
+
};
|
|
121
|
+
}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (isInterfaceType(type)) {
|
|
125
|
+
const types = Object.values(schema.getTypeMap()).filter(t => isObjectType(t) && t.getInterfaces().includes(type));
|
|
126
|
+
return {
|
|
127
|
+
kind: Kind.SELECTION_SET,
|
|
128
|
+
selections: types
|
|
129
|
+
.filter(t => !hasCircularRef([...ancestors, t], {
|
|
130
|
+
depth: depthLimit,
|
|
131
|
+
}))
|
|
132
|
+
.map(t => {
|
|
133
|
+
return {
|
|
134
|
+
kind: Kind.INLINE_FRAGMENT,
|
|
135
|
+
typeCondition: {
|
|
136
|
+
kind: Kind.NAMED_TYPE,
|
|
137
|
+
name: {
|
|
138
|
+
kind: Kind.NAME,
|
|
139
|
+
value: t.name,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
selectionSet: resolveSelectionSet({
|
|
143
|
+
parent: type,
|
|
144
|
+
type: t,
|
|
145
|
+
models,
|
|
146
|
+
path,
|
|
147
|
+
ancestors,
|
|
148
|
+
ignore,
|
|
149
|
+
depthLimit,
|
|
150
|
+
schema,
|
|
151
|
+
}),
|
|
152
|
+
};
|
|
153
|
+
}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (isObjectType(type)) {
|
|
157
|
+
const isIgnored = ignore.includes(type.name) ||
|
|
158
|
+
ignore.includes(`${parent.name}.${path[path.length - 1]}`);
|
|
159
|
+
const isModel = models.includes(type.name);
|
|
160
|
+
if (!firstCall && isModel && !isIgnored) {
|
|
161
|
+
return {
|
|
162
|
+
kind: Kind.SELECTION_SET,
|
|
163
|
+
selections: [
|
|
164
|
+
{
|
|
165
|
+
kind: Kind.FIELD,
|
|
166
|
+
name: {
|
|
167
|
+
kind: Kind.NAME,
|
|
168
|
+
value: 'id',
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const fields = type.getFields();
|
|
175
|
+
return {
|
|
176
|
+
kind: Kind.SELECTION_SET,
|
|
177
|
+
selections: Object.keys(fields)
|
|
178
|
+
.filter(fieldName => {
|
|
179
|
+
return !hasCircularRef([...ancestors, getNamedType(fields[fieldName].type)], {
|
|
180
|
+
depth: depthLimit,
|
|
181
|
+
});
|
|
182
|
+
})
|
|
183
|
+
.map(fieldName => {
|
|
184
|
+
return resolveField({
|
|
185
|
+
type: type,
|
|
186
|
+
field: fields[fieldName],
|
|
187
|
+
models,
|
|
188
|
+
path: [...path, fieldName],
|
|
189
|
+
ancestors,
|
|
190
|
+
ignore,
|
|
191
|
+
depthLimit,
|
|
192
|
+
schema,
|
|
193
|
+
});
|
|
194
|
+
}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function resolveVariable(arg, name) {
|
|
199
|
+
function resolveVariableType(type) {
|
|
200
|
+
if (isListType(type)) {
|
|
201
|
+
return {
|
|
202
|
+
kind: Kind.LIST_TYPE,
|
|
203
|
+
type: resolveVariableType(type.ofType),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (isNonNullType(type)) {
|
|
207
|
+
return {
|
|
208
|
+
kind: Kind.NON_NULL_TYPE,
|
|
209
|
+
type: resolveVariableType(type.ofType),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
kind: Kind.NAMED_TYPE,
|
|
214
|
+
name: {
|
|
215
|
+
kind: Kind.NAME,
|
|
216
|
+
value: type.name,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
kind: Kind.VARIABLE_DEFINITION,
|
|
222
|
+
variable: {
|
|
223
|
+
kind: Kind.VARIABLE,
|
|
224
|
+
name: {
|
|
225
|
+
kind: Kind.NAME,
|
|
226
|
+
value: name || arg.name,
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
type: resolveVariableType(arg.type),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function getArgumentName(name, path) {
|
|
233
|
+
return camelCase([...path, name].join('_'));
|
|
234
|
+
}
|
|
235
|
+
function resolveField({ type, field, models, firstCall, path, ancestors, ignore, depthLimit, schema, }) {
|
|
236
|
+
const namedType = getNamedType(field.type);
|
|
237
|
+
let args = [];
|
|
238
|
+
if (field.args && field.args.length) {
|
|
239
|
+
args = field.args.map(arg => {
|
|
240
|
+
if (!firstCall) {
|
|
241
|
+
addOperationVariable(resolveVariable(arg, getArgumentName(arg.name, path)));
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
kind: Kind.ARGUMENT,
|
|
245
|
+
name: {
|
|
246
|
+
kind: Kind.NAME,
|
|
247
|
+
value: arg.name,
|
|
248
|
+
},
|
|
249
|
+
value: {
|
|
250
|
+
kind: Kind.VARIABLE,
|
|
251
|
+
name: {
|
|
252
|
+
kind: Kind.NAME,
|
|
253
|
+
value: getArgumentName(arg.name, path),
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (!isScalarType(namedType)) {
|
|
260
|
+
return {
|
|
261
|
+
kind: Kind.FIELD,
|
|
262
|
+
name: {
|
|
263
|
+
kind: Kind.NAME,
|
|
264
|
+
value: field.name,
|
|
265
|
+
},
|
|
266
|
+
selectionSet: resolveSelectionSet({
|
|
267
|
+
parent: type,
|
|
268
|
+
type: namedType,
|
|
269
|
+
models,
|
|
270
|
+
firstCall,
|
|
271
|
+
path: [...path, field.name],
|
|
272
|
+
ancestors: [...ancestors, type],
|
|
273
|
+
ignore,
|
|
274
|
+
depthLimit,
|
|
275
|
+
schema,
|
|
276
|
+
}),
|
|
277
|
+
arguments: args,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
kind: Kind.FIELD,
|
|
282
|
+
name: {
|
|
283
|
+
kind: Kind.NAME,
|
|
284
|
+
value: field.name,
|
|
285
|
+
},
|
|
286
|
+
arguments: args,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function hasCircularRef(types, config = {
|
|
290
|
+
depth: 1,
|
|
291
|
+
}) {
|
|
292
|
+
const type = types[types.length - 1];
|
|
293
|
+
if (isScalarType(type)) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
const size = types.filter(t => t.name === type.name).length;
|
|
297
|
+
return size > config.depth;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function getOperationInfo(doc) {
|
|
301
|
+
const op = getOperationAST(doc, null);
|
|
302
|
+
if (!op) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
operation: op,
|
|
307
|
+
name: op.name.value,
|
|
308
|
+
variables: op.variableDefinitions || [],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function convertName(name) {
|
|
313
|
+
return paramCase(name);
|
|
314
|
+
}
|
|
315
|
+
function isNil(val) {
|
|
316
|
+
return val == null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function createSofa(config) {
|
|
320
|
+
logger.debug('[Sofa] Created');
|
|
321
|
+
const models = extractsModels(config.schema);
|
|
322
|
+
const ignore = config.ignore || [];
|
|
323
|
+
logger.debug(`[Sofa] models: ${models.join(', ')}`);
|
|
324
|
+
logger.debug(`[Sofa] ignore: ${ignore.join(', ')}`);
|
|
325
|
+
return Object.assign({ context({ req }) {
|
|
326
|
+
return { req };
|
|
327
|
+
}, execute: graphql, models,
|
|
328
|
+
ignore }, config);
|
|
329
|
+
}
|
|
330
|
+
// Objects and Unions are the only things that are used to define return types
|
|
331
|
+
// and both might contain an ID
|
|
332
|
+
// We don't treat Unions as models because
|
|
333
|
+
// they might represent an Object that is not a model
|
|
334
|
+
// We check it later, when an operation is being built
|
|
335
|
+
function extractsModels(schema) {
|
|
336
|
+
const modelMap = {};
|
|
337
|
+
const query = schema.getQueryType();
|
|
338
|
+
const fields = query.getFields();
|
|
339
|
+
// if Query[type] (no args) and Query[type](just id as an argument)
|
|
340
|
+
// loop through every field
|
|
341
|
+
for (const fieldName in fields) {
|
|
342
|
+
const field = fields[fieldName];
|
|
343
|
+
const namedType = getNamedType(field.type);
|
|
344
|
+
if (hasID(namedType)) {
|
|
345
|
+
if (!modelMap[namedType.name]) {
|
|
346
|
+
modelMap[namedType.name] = {};
|
|
347
|
+
}
|
|
348
|
+
if (isArrayOf(field.type, namedType)) {
|
|
349
|
+
// check if type is a list
|
|
350
|
+
// check if name of a field matches a name of a named type (in plural)
|
|
351
|
+
// check if has no non-optional arguments
|
|
352
|
+
// add to registry with `list: true`
|
|
353
|
+
const sameName = isNameEqual(field.name, namedType.name + 's');
|
|
354
|
+
const allOptionalArguments = !field.args.some(arg => isNonNullType(arg.type));
|
|
355
|
+
modelMap[namedType.name].list = sameName && allOptionalArguments;
|
|
356
|
+
}
|
|
357
|
+
else if (isObjectType(field.type) ||
|
|
358
|
+
(isNonNullType(field.type) && isObjectType(field.type.ofType))) {
|
|
359
|
+
// check if type is a graphql object type
|
|
360
|
+
// check if name of a field matches with name of an object type
|
|
361
|
+
// check if has only one argument named `id`
|
|
362
|
+
// add to registry with `single: true`
|
|
363
|
+
const sameName = isNameEqual(field.name, namedType.name);
|
|
364
|
+
const hasIdArgument = field.args.length === 1 && field.args[0].name === 'id';
|
|
365
|
+
modelMap[namedType.name].single = sameName && hasIdArgument;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return Object.keys(modelMap).filter(name => modelMap[name].list && modelMap[name].single);
|
|
370
|
+
}
|
|
371
|
+
// it's dumb but let's leave it for now
|
|
372
|
+
function isArrayOf(type, expected) {
|
|
373
|
+
if (isOptionalList(type)) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (isNonNullType(type) && isOptionalList(type.ofType)) {
|
|
377
|
+
return true;
|
|
378
|
+
}
|
|
379
|
+
function isOptionalList(list) {
|
|
380
|
+
if (isListType(list)) {
|
|
381
|
+
if (list.ofType.name === expected.name) {
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
if (isNonNullType(list.ofType) &&
|
|
385
|
+
list.ofType.ofType.name === expected.name) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
function hasID(type) {
|
|
393
|
+
return isObjectType(type) && !!type.getFields().id;
|
|
394
|
+
}
|
|
395
|
+
function isNameEqual(a, b) {
|
|
396
|
+
return convertName(a) === convertName(b);
|
|
397
|
+
}
|
|
398
|
+
function isContextFn(context) {
|
|
399
|
+
return typeof context === 'function';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function parseVariable({ value, variable, schema, }) {
|
|
403
|
+
if (isNil(value)) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
return resolveVariable$1({
|
|
407
|
+
value,
|
|
408
|
+
type: variable.type,
|
|
409
|
+
schema,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
function resolveVariable$1({ value, type, schema, }) {
|
|
413
|
+
if (type.kind === Kind.NAMED_TYPE) {
|
|
414
|
+
const namedType = schema.getType(type.name.value);
|
|
415
|
+
if (isScalarType(namedType)) {
|
|
416
|
+
// GraphQLBoolean.serialize expects a boolean or a number only
|
|
417
|
+
if (isEqualType(GraphQLBoolean, namedType)) {
|
|
418
|
+
// we don't support TRUE
|
|
419
|
+
value = value === 'true';
|
|
420
|
+
}
|
|
421
|
+
return namedType.serialize(value);
|
|
422
|
+
}
|
|
423
|
+
if (isInputObjectType(namedType)) {
|
|
424
|
+
return value && typeof value === 'object' ? value : JSON.parse(value);
|
|
425
|
+
}
|
|
426
|
+
return value;
|
|
427
|
+
}
|
|
428
|
+
if (type.kind === Kind.LIST_TYPE) {
|
|
429
|
+
return value.map(val => resolveVariable$1({
|
|
430
|
+
value: val,
|
|
431
|
+
type: type.type,
|
|
432
|
+
schema,
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
if (type.kind === Kind.NON_NULL_TYPE) {
|
|
436
|
+
return resolveVariable$1({
|
|
437
|
+
value: value,
|
|
438
|
+
type: type.type,
|
|
439
|
+
schema,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
class SubscriptionManager {
|
|
445
|
+
constructor(sofa) {
|
|
446
|
+
this.sofa = sofa;
|
|
447
|
+
this.operations = new Map();
|
|
448
|
+
this.clients = new Map();
|
|
449
|
+
this.buildOperations();
|
|
450
|
+
}
|
|
451
|
+
start(event, { req, res, }) {
|
|
452
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
453
|
+
const id = v4();
|
|
454
|
+
const name = event.subscription;
|
|
455
|
+
if (!this.operations.has(name)) {
|
|
456
|
+
throw new Error(`Subscription '${name}' is not available`);
|
|
457
|
+
}
|
|
458
|
+
const { document, operationName, variables } = this.operations.get(name);
|
|
459
|
+
logger.info(`[Subscription] Start ${id}`, event);
|
|
460
|
+
const result = yield this.execute({
|
|
461
|
+
id,
|
|
462
|
+
name,
|
|
463
|
+
url: event.url,
|
|
464
|
+
document,
|
|
465
|
+
operationName,
|
|
466
|
+
variables,
|
|
467
|
+
req,
|
|
468
|
+
res,
|
|
469
|
+
});
|
|
470
|
+
if (typeof result !== 'undefined') {
|
|
471
|
+
return result;
|
|
472
|
+
}
|
|
473
|
+
return { id };
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
stop(id) {
|
|
477
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
478
|
+
logger.info(`[Subscription] Stop ${id}`);
|
|
479
|
+
if (!this.clients.has(id)) {
|
|
480
|
+
throw new Error(`Subscription with ID '${id}' does not exist`);
|
|
481
|
+
}
|
|
482
|
+
const execution = this.clients.get(id);
|
|
483
|
+
if (execution.iterator.return) {
|
|
484
|
+
execution.iterator.return();
|
|
485
|
+
}
|
|
486
|
+
this.clients.delete(id);
|
|
487
|
+
return { id };
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
update(event, { req, res, }) {
|
|
491
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
492
|
+
const { variables, id } = event;
|
|
493
|
+
logger.info(`[Subscription] Update ${id}`, event);
|
|
494
|
+
if (!this.clients.has(id)) {
|
|
495
|
+
throw new Error(`Subscription with ID '${id}' does not exist`);
|
|
496
|
+
}
|
|
497
|
+
const { name: subscription, url } = this.clients.get(id);
|
|
498
|
+
this.stop(id);
|
|
499
|
+
return this.start({
|
|
500
|
+
url,
|
|
501
|
+
subscription,
|
|
502
|
+
variables,
|
|
503
|
+
}, {
|
|
504
|
+
req,
|
|
505
|
+
res,
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
execute({ id, document, name, url, operationName, variables, req, res, }) {
|
|
510
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
511
|
+
const variableNodes = this.operations.get(name).variables;
|
|
512
|
+
const variableValues = variableNodes.reduce((values, variable) => {
|
|
513
|
+
const value = parseVariable({
|
|
514
|
+
value: variables[variable.variable.name.value],
|
|
515
|
+
variable,
|
|
516
|
+
schema: this.sofa.schema,
|
|
517
|
+
});
|
|
518
|
+
if (typeof value === 'undefined') {
|
|
519
|
+
return values;
|
|
520
|
+
}
|
|
521
|
+
return Object.assign(Object.assign({}, values), { [name]: value });
|
|
522
|
+
}, {});
|
|
523
|
+
const C = isContextFn(this.sofa.context)
|
|
524
|
+
? yield this.sofa.context({ req, res })
|
|
525
|
+
: this.sofa.context;
|
|
526
|
+
const execution = yield subscribe({
|
|
527
|
+
schema: this.sofa.schema,
|
|
528
|
+
document,
|
|
529
|
+
operationName,
|
|
530
|
+
variableValues,
|
|
531
|
+
contextValue: C,
|
|
532
|
+
});
|
|
533
|
+
if (isAsyncIterable(execution)) {
|
|
534
|
+
// successful
|
|
535
|
+
// add execution to clients
|
|
536
|
+
this.clients.set(id, {
|
|
537
|
+
name,
|
|
538
|
+
url,
|
|
539
|
+
iterator: execution,
|
|
540
|
+
});
|
|
541
|
+
// success
|
|
542
|
+
forAwaitEach(execution, (result) => __awaiter(this, void 0, void 0, function* () {
|
|
543
|
+
yield this.sendData({
|
|
544
|
+
id,
|
|
545
|
+
result,
|
|
546
|
+
});
|
|
547
|
+
})).then(() => {
|
|
548
|
+
// completes
|
|
549
|
+
this.stop(id);
|
|
550
|
+
}, e => {
|
|
551
|
+
logger.info(`Subscription #${id} closed`);
|
|
552
|
+
logger.error(e);
|
|
553
|
+
this.stop(id);
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
return execution;
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
sendData({ id, result }) {
|
|
562
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
563
|
+
if (!this.clients.has(id)) {
|
|
564
|
+
throw new Error(`Subscription with ID '${id}' does not exist`);
|
|
565
|
+
}
|
|
566
|
+
const { url } = this.clients.get(id);
|
|
567
|
+
logger.info(`[Subscription] Trigger ${id}`);
|
|
568
|
+
yield axios.post(url, result);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
buildOperations() {
|
|
572
|
+
const subscription = this.sofa.schema.getSubscriptionType();
|
|
573
|
+
if (!subscription) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
const fieldMap = subscription.getFields();
|
|
577
|
+
for (const field in fieldMap) {
|
|
578
|
+
const document = buildOperation({
|
|
579
|
+
kind: 'subscription',
|
|
580
|
+
field,
|
|
581
|
+
schema: this.sofa.schema,
|
|
582
|
+
models: this.sofa.models,
|
|
583
|
+
ignore: this.sofa.ignore,
|
|
584
|
+
});
|
|
585
|
+
const { variables, name: operationName } = getOperationInfo(document);
|
|
586
|
+
this.operations.set(field, {
|
|
587
|
+
operationName,
|
|
588
|
+
document,
|
|
589
|
+
variables,
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function createRouter(sofa) {
|
|
596
|
+
logger.debug('[Sofa] Creating router');
|
|
597
|
+
const router = Router();
|
|
598
|
+
const queryType = sofa.schema.getQueryType();
|
|
599
|
+
const mutationType = sofa.schema.getMutationType();
|
|
600
|
+
const subscriptionManager = new SubscriptionManager(sofa);
|
|
601
|
+
if (queryType) {
|
|
602
|
+
Object.keys(queryType.getFields()).forEach(fieldName => {
|
|
603
|
+
const route = createQueryRoute({ sofa, router, fieldName });
|
|
604
|
+
if (sofa.onRoute) {
|
|
605
|
+
sofa.onRoute(route);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
if (mutationType) {
|
|
610
|
+
Object.keys(mutationType.getFields()).forEach(fieldName => {
|
|
611
|
+
const route = createMutationRoute({ sofa, router, fieldName });
|
|
612
|
+
if (sofa.onRoute) {
|
|
613
|
+
sofa.onRoute(route);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
router.post('/webhook', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
618
|
+
const { subscription, variables, url } = req.body;
|
|
619
|
+
try {
|
|
620
|
+
const result = yield subscriptionManager.start({
|
|
621
|
+
subscription,
|
|
622
|
+
variables,
|
|
623
|
+
url,
|
|
624
|
+
}, { req, res });
|
|
625
|
+
res.statusCode = 200;
|
|
626
|
+
res.statusMessage = 'OK';
|
|
627
|
+
res.json(result);
|
|
628
|
+
}
|
|
629
|
+
catch (e) {
|
|
630
|
+
res.statusCode = 500;
|
|
631
|
+
res.statusMessage = 'Subscription failed';
|
|
632
|
+
res.json(e);
|
|
633
|
+
}
|
|
634
|
+
})));
|
|
635
|
+
router.post('/webhook/:id', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
636
|
+
const id = req.params.id;
|
|
637
|
+
const variables = req.body.variables;
|
|
638
|
+
try {
|
|
639
|
+
const result = yield subscriptionManager.update({
|
|
640
|
+
id,
|
|
641
|
+
variables,
|
|
642
|
+
}, {
|
|
643
|
+
req,
|
|
644
|
+
res,
|
|
645
|
+
});
|
|
646
|
+
res.statusCode = 200;
|
|
647
|
+
res.statusMessage = 'OK';
|
|
648
|
+
res.json(result);
|
|
649
|
+
}
|
|
650
|
+
catch (e) {
|
|
651
|
+
res.statusCode = 500;
|
|
652
|
+
res.statusMessage = 'Subscription failed to update';
|
|
653
|
+
res.json(e);
|
|
654
|
+
}
|
|
655
|
+
})));
|
|
656
|
+
router.delete('/webhook/:id', useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
657
|
+
const id = req.params.id;
|
|
658
|
+
try {
|
|
659
|
+
const result = yield subscriptionManager.stop(id);
|
|
660
|
+
res.statusCode = 200;
|
|
661
|
+
res.statusMessage = 'OK';
|
|
662
|
+
res.json(result);
|
|
663
|
+
}
|
|
664
|
+
catch (e) {
|
|
665
|
+
res.statusCode = 500;
|
|
666
|
+
res.statusMessage = 'Subscription failed to stop';
|
|
667
|
+
res.json(e);
|
|
668
|
+
}
|
|
669
|
+
})));
|
|
670
|
+
return router;
|
|
671
|
+
}
|
|
672
|
+
function createQueryRoute({ sofa, router, fieldName, }) {
|
|
673
|
+
logger.debug(`[Router] Creating ${fieldName} query`);
|
|
674
|
+
const queryType = sofa.schema.getQueryType();
|
|
675
|
+
const operation = buildOperation({
|
|
676
|
+
kind: 'query',
|
|
677
|
+
schema: sofa.schema,
|
|
678
|
+
field: fieldName,
|
|
679
|
+
models: sofa.models,
|
|
680
|
+
ignore: sofa.ignore,
|
|
681
|
+
});
|
|
682
|
+
const info = getOperationInfo(operation);
|
|
683
|
+
const field = queryType.getFields()[fieldName];
|
|
684
|
+
const fieldType = field.type;
|
|
685
|
+
const isSingle = isObjectType(fieldType) ||
|
|
686
|
+
(isNonNullType(fieldType) && isObjectType(fieldType.ofType));
|
|
687
|
+
const hasIdArgument = field.args.some(arg => arg.name === 'id');
|
|
688
|
+
const path = getPath(fieldName, isSingle && hasIdArgument);
|
|
689
|
+
const method = produceMethod({
|
|
690
|
+
typeName: queryType.name,
|
|
691
|
+
fieldName,
|
|
692
|
+
methodMap: sofa.method,
|
|
693
|
+
defaultValue: 'GET',
|
|
694
|
+
});
|
|
695
|
+
router[method.toLocaleLowerCase()](path, useHandler({ info, fieldName, sofa, operation }));
|
|
696
|
+
logger.debug(`[Router] ${fieldName} query available at ${method} ${path}`);
|
|
697
|
+
return {
|
|
698
|
+
document: operation,
|
|
699
|
+
path,
|
|
700
|
+
method: method.toUpperCase(),
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function createMutationRoute({ sofa, router, fieldName, }) {
|
|
704
|
+
logger.debug(`[Router] Creating ${fieldName} mutation`);
|
|
705
|
+
const mutationType = sofa.schema.getMutationType();
|
|
706
|
+
const operation = buildOperation({
|
|
707
|
+
kind: 'mutation',
|
|
708
|
+
schema: sofa.schema,
|
|
709
|
+
field: fieldName,
|
|
710
|
+
models: sofa.models,
|
|
711
|
+
ignore: sofa.ignore,
|
|
712
|
+
});
|
|
713
|
+
const info = getOperationInfo(operation);
|
|
714
|
+
const path = getPath(fieldName);
|
|
715
|
+
const method = produceMethod({
|
|
716
|
+
typeName: mutationType.name,
|
|
717
|
+
fieldName,
|
|
718
|
+
methodMap: sofa.method,
|
|
719
|
+
defaultValue: 'POST',
|
|
720
|
+
});
|
|
721
|
+
router[method.toLowerCase()](path, useHandler({ info, fieldName, sofa, operation }));
|
|
722
|
+
logger.debug(`[Router] ${fieldName} mutation available at ${method} ${path}`);
|
|
723
|
+
return {
|
|
724
|
+
document: operation,
|
|
725
|
+
path,
|
|
726
|
+
method: method.toUpperCase(),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
function useHandler(config) {
|
|
730
|
+
const { sofa, operation, fieldName } = config;
|
|
731
|
+
const info = config.info;
|
|
732
|
+
return useAsync((req, res) => __awaiter(this, void 0, void 0, function* () {
|
|
733
|
+
const variableValues = info.variables.reduce((variables, variable) => {
|
|
734
|
+
const name = variable.variable.name.value;
|
|
735
|
+
const value = parseVariable({
|
|
736
|
+
value: pickParam(req, name),
|
|
737
|
+
variable,
|
|
738
|
+
schema: sofa.schema,
|
|
739
|
+
});
|
|
740
|
+
if (typeof value === 'undefined') {
|
|
741
|
+
return variables;
|
|
742
|
+
}
|
|
743
|
+
return Object.assign(Object.assign({}, variables), { [name]: value });
|
|
744
|
+
}, {});
|
|
745
|
+
const contextValue = isContextFn(sofa.context)
|
|
746
|
+
? yield sofa.context({ req, res })
|
|
747
|
+
: sofa.context;
|
|
748
|
+
const result = yield sofa.execute({
|
|
749
|
+
schema: sofa.schema,
|
|
750
|
+
source: print(operation),
|
|
751
|
+
contextValue,
|
|
752
|
+
variableValues,
|
|
753
|
+
operationName: info.operation.name && info.operation.name.value,
|
|
754
|
+
});
|
|
755
|
+
if (result.errors) {
|
|
756
|
+
const defaultErrorHandler = (res, errors) => {
|
|
757
|
+
res.statusCode = 500;
|
|
758
|
+
res.json(errors[0]);
|
|
759
|
+
};
|
|
760
|
+
const errorHandler = sofa.errorHandler || defaultErrorHandler;
|
|
761
|
+
errorHandler(res, result.errors);
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
res.json(result.data && result.data[fieldName]);
|
|
765
|
+
}));
|
|
766
|
+
}
|
|
767
|
+
function getPath(fieldName, hasId = false) {
|
|
768
|
+
return `/${convertName(fieldName)}${hasId ? '/:id' : ''}`;
|
|
769
|
+
}
|
|
770
|
+
function pickParam(req, name) {
|
|
771
|
+
if (req.params && req.params.hasOwnProperty(name)) {
|
|
772
|
+
return req.params[name];
|
|
773
|
+
}
|
|
774
|
+
if (req.query && req.query.hasOwnProperty(name)) {
|
|
775
|
+
return req.query[name];
|
|
776
|
+
}
|
|
777
|
+
if (req.body && req.body.hasOwnProperty(name)) {
|
|
778
|
+
return req.body[name];
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function useAsync(handler) {
|
|
782
|
+
return (req, res, next) => {
|
|
783
|
+
Promise.resolve(handler(req, res, next)).catch(e => next(e));
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
function produceMethod({ typeName, fieldName, methodMap, defaultValue, }) {
|
|
787
|
+
const path = `${typeName}.${fieldName}`;
|
|
788
|
+
if (methodMap && methodMap[path]) {
|
|
789
|
+
return methodMap[path];
|
|
790
|
+
}
|
|
791
|
+
return defaultValue;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function mapToPrimitive(type) {
|
|
795
|
+
const formatMap = {
|
|
796
|
+
Int: {
|
|
797
|
+
type: 'integer',
|
|
798
|
+
format: 'int32',
|
|
799
|
+
},
|
|
800
|
+
Float: {
|
|
801
|
+
type: 'number',
|
|
802
|
+
format: 'float',
|
|
803
|
+
},
|
|
804
|
+
String: {
|
|
805
|
+
type: 'string',
|
|
806
|
+
},
|
|
807
|
+
Boolean: {
|
|
808
|
+
type: 'boolean',
|
|
809
|
+
},
|
|
810
|
+
ID: {
|
|
811
|
+
type: 'string',
|
|
812
|
+
},
|
|
813
|
+
};
|
|
814
|
+
if (formatMap[type]) {
|
|
815
|
+
return formatMap[type];
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function mapToRef(type) {
|
|
819
|
+
return `#/components/schemas/${type}`;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function buildSchemaObjectFromType(type) {
|
|
823
|
+
const required = [];
|
|
824
|
+
const properties = {};
|
|
825
|
+
const fields = type.getFields();
|
|
826
|
+
for (const fieldName in fields) {
|
|
827
|
+
const field = fields[fieldName];
|
|
828
|
+
if (isNonNullType(field.type)) {
|
|
829
|
+
required.push(field.name);
|
|
830
|
+
}
|
|
831
|
+
properties[fieldName] = resolveField$1(field);
|
|
832
|
+
if (field.description) {
|
|
833
|
+
properties[fieldName].description = field.description;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return Object.assign(Object.assign(Object.assign({ type: 'object' }, (required.length ? { required } : {})), { properties }), (type.description ? { description: type.description } : {}));
|
|
837
|
+
}
|
|
838
|
+
function resolveField$1(field) {
|
|
839
|
+
return resolveFieldType(field.type);
|
|
840
|
+
}
|
|
841
|
+
// array -> [type]
|
|
842
|
+
// type -> $ref
|
|
843
|
+
// scalar -> swagger primitive
|
|
844
|
+
function resolveFieldType(type) {
|
|
845
|
+
if (isNonNullType(type)) {
|
|
846
|
+
return resolveFieldType(type.ofType);
|
|
847
|
+
}
|
|
848
|
+
if (isListType(type)) {
|
|
849
|
+
return {
|
|
850
|
+
type: 'array',
|
|
851
|
+
items: resolveFieldType(type.ofType),
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
if (isObjectType(type)) {
|
|
855
|
+
return {
|
|
856
|
+
$ref: mapToRef(type.name),
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
if (isScalarType(type)) {
|
|
860
|
+
return (mapToPrimitive(type.name) || {
|
|
861
|
+
type: 'object',
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return {
|
|
865
|
+
type: 'object',
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function buildPathFromOperation({ url, schema, operation, useRequestBody, }) {
|
|
870
|
+
const info = getOperationInfo(operation);
|
|
871
|
+
const description = resolveDescription(schema, info.operation);
|
|
872
|
+
return Object.assign(Object.assign({ operationId: info.name }, (useRequestBody
|
|
873
|
+
? {
|
|
874
|
+
requestBody: {
|
|
875
|
+
content: {
|
|
876
|
+
'application/json': {
|
|
877
|
+
schema: resolveRequestBody(info.operation.variableDefinitions),
|
|
878
|
+
},
|
|
879
|
+
},
|
|
880
|
+
},
|
|
881
|
+
}
|
|
882
|
+
: {
|
|
883
|
+
parameters: resolveParameters(url, info.operation.variableDefinitions),
|
|
884
|
+
})), { responses: {
|
|
885
|
+
200: {
|
|
886
|
+
description,
|
|
887
|
+
content: {
|
|
888
|
+
'application/json': {
|
|
889
|
+
schema: resolveResponse({
|
|
890
|
+
schema,
|
|
891
|
+
operation: info.operation,
|
|
892
|
+
}),
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
},
|
|
896
|
+
} });
|
|
897
|
+
}
|
|
898
|
+
function resolveParameters(url, variables) {
|
|
899
|
+
if (!variables) {
|
|
900
|
+
return [];
|
|
901
|
+
}
|
|
902
|
+
return variables.map((variable) => {
|
|
903
|
+
return {
|
|
904
|
+
in: isInPath(url, variable.variable.name.value) ? 'path' : 'query',
|
|
905
|
+
name: variable.variable.name.value,
|
|
906
|
+
required: variable.type.kind === Kind.NON_NULL_TYPE,
|
|
907
|
+
schema: resolveParamSchema(variable.type),
|
|
908
|
+
};
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
function resolveRequestBody(variables) {
|
|
912
|
+
if (!variables) {
|
|
913
|
+
return {};
|
|
914
|
+
}
|
|
915
|
+
const properties = {};
|
|
916
|
+
const required = [];
|
|
917
|
+
variables.forEach(variable => {
|
|
918
|
+
if (variable.type.kind === Kind.NON_NULL_TYPE) {
|
|
919
|
+
required.push(variable.variable.name.value);
|
|
920
|
+
}
|
|
921
|
+
properties[variable.variable.name.value] = resolveParamSchema(variable.type);
|
|
922
|
+
});
|
|
923
|
+
return Object.assign({ type: 'object', properties }, (required.length ? { required } : {}));
|
|
924
|
+
}
|
|
925
|
+
// array -> [type]
|
|
926
|
+
// type -> $ref
|
|
927
|
+
// scalar -> swagger primitive
|
|
928
|
+
function resolveParamSchema(type) {
|
|
929
|
+
if (type.kind === Kind.NON_NULL_TYPE) {
|
|
930
|
+
return resolveParamSchema(type.type);
|
|
931
|
+
}
|
|
932
|
+
if (type.kind === Kind.LIST_TYPE) {
|
|
933
|
+
return {
|
|
934
|
+
type: 'array',
|
|
935
|
+
items: resolveParamSchema(type.type),
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
const primitive = mapToPrimitive(type.name.value);
|
|
939
|
+
return (primitive || {
|
|
940
|
+
$ref: mapToRef(type.name.value),
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
function resolveResponse({ schema, operation, }) {
|
|
944
|
+
const operationType = operation.operation;
|
|
945
|
+
const rootField = operation.selectionSet.selections[0];
|
|
946
|
+
if (rootField.kind === Kind.FIELD) {
|
|
947
|
+
if (operationType === 'query') {
|
|
948
|
+
const queryType = schema.getQueryType();
|
|
949
|
+
const field = queryType.getFields()[rootField.name.value];
|
|
950
|
+
return resolveFieldType(field.type);
|
|
951
|
+
}
|
|
952
|
+
if (operationType === 'mutation') {
|
|
953
|
+
const mutationType = schema.getMutationType();
|
|
954
|
+
const field = mutationType.getFields()[rootField.name.value];
|
|
955
|
+
return resolveFieldType(field.type);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function isInPath(url, param) {
|
|
960
|
+
return url.indexOf(`{${param}}`) !== -1;
|
|
961
|
+
}
|
|
962
|
+
function resolveDescription(schema, operation) {
|
|
963
|
+
const selection = operation.selectionSet.selections[0];
|
|
964
|
+
const fieldName = selection.name.value;
|
|
965
|
+
const typeDefinition = schema.getType(titleCase(operation.operation));
|
|
966
|
+
if (!typeDefinition) {
|
|
967
|
+
return '';
|
|
968
|
+
}
|
|
969
|
+
const definitionNode = typeDefinition.astNode || parse(printType(typeDefinition)).definitions[0];
|
|
970
|
+
if (!isObjectTypeDefinitionNode(definitionNode)) {
|
|
971
|
+
return '';
|
|
972
|
+
}
|
|
973
|
+
const fieldNode = definitionNode.fields.find(field => field.name.value === fieldName);
|
|
974
|
+
const descriptionDefinition = fieldNode && fieldNode.description;
|
|
975
|
+
return descriptionDefinition && descriptionDefinition.value
|
|
976
|
+
? descriptionDefinition.value
|
|
977
|
+
: '';
|
|
978
|
+
}
|
|
979
|
+
function isObjectTypeDefinitionNode(node) {
|
|
980
|
+
return node.kind === Kind.OBJECT_TYPE_DEFINITION;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function OpenAPI({ schema, info, components, security, }) {
|
|
984
|
+
const types = schema.getTypeMap();
|
|
985
|
+
const swagger = {
|
|
986
|
+
openapi: '3.0.0',
|
|
987
|
+
info,
|
|
988
|
+
paths: {},
|
|
989
|
+
components: {
|
|
990
|
+
schemas: {},
|
|
991
|
+
},
|
|
992
|
+
};
|
|
993
|
+
for (const typeName in types) {
|
|
994
|
+
const type = types[typeName];
|
|
995
|
+
if ((isObjectType(type) || isInputObjectType(type)) &&
|
|
996
|
+
!isIntrospectionType(type)) {
|
|
997
|
+
swagger.components.schemas[typeName] = buildSchemaObjectFromType(type);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
if (components) {
|
|
1001
|
+
swagger.components = Object.assign(Object.assign({}, components), swagger.components);
|
|
1002
|
+
}
|
|
1003
|
+
if (security) {
|
|
1004
|
+
swagger.security = security;
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
addRoute(info, config) {
|
|
1008
|
+
const basePath = (config === null || config === void 0 ? void 0 : config.basePath) || '';
|
|
1009
|
+
const path = basePath +
|
|
1010
|
+
info.path.replace(/\:[a-z0-9]+\w/i, param => `{${param.replace(':', '')}}`);
|
|
1011
|
+
if (!swagger.paths[path]) {
|
|
1012
|
+
swagger.paths[path] = {};
|
|
1013
|
+
}
|
|
1014
|
+
swagger.paths[path][info.method.toLowerCase()] = buildPathFromOperation({
|
|
1015
|
+
url: path,
|
|
1016
|
+
operation: info.document,
|
|
1017
|
+
schema,
|
|
1018
|
+
useRequestBody: ['POST', 'PUT', 'PATCH'].includes(info.method),
|
|
1019
|
+
});
|
|
1020
|
+
},
|
|
1021
|
+
get() {
|
|
1022
|
+
return swagger;
|
|
1023
|
+
},
|
|
1024
|
+
save(filepath) {
|
|
1025
|
+
const isJSON = /\.json$/i;
|
|
1026
|
+
const isYAML = /.ya?ml$/i;
|
|
1027
|
+
if (isJSON.test(filepath)) {
|
|
1028
|
+
writeOutput(filepath, JSON.stringify(swagger, null, 2));
|
|
1029
|
+
}
|
|
1030
|
+
else if (isYAML.test(filepath)) {
|
|
1031
|
+
writeOutput(filepath, stringify(swagger, Infinity));
|
|
1032
|
+
}
|
|
1033
|
+
else {
|
|
1034
|
+
throw new Error('We only support JSON and YAML files');
|
|
1035
|
+
}
|
|
1036
|
+
},
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
function writeOutput(filepath, contents) {
|
|
1040
|
+
writeFileSync(filepath, contents, {
|
|
1041
|
+
encoding: 'utf-8',
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function useSofa(config) {
|
|
1046
|
+
return createRouter(createSofa(config));
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
export default useSofa;
|
|
1050
|
+
export { OpenAPI, createSofa, useSofa };
|