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