UNPKG

17.7 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6var _exportNames = {
7 decorators: true,
8 Resolvers: true
9};
10Object.defineProperty(exports, "Resolvers", {
11 enumerable: true,
12 get: function () {
13 return _Resolvers.default;
14 }
15});
16exports.decorators = exports.default = void 0;
17
18var _easevalidation = require("easevalidation");
19
20var _graphql = require("graphql");
21
22var _glob = _interopRequireDefault(require("glob"));
23
24var _path = _interopRequireDefault(require("path"));
25
26var _fsExtra = _interopRequireDefault(require("fs-extra"));
27
28var _get = _interopRequireDefault(require("lodash/get"));
29
30var _set = _interopRequireDefault(require("lodash/set"));
31
32var _has = _interopRequireDefault(require("lodash/has"));
33
34var _pick = _interopRequireDefault(require("lodash/pick"));
35
36var _isPlainObject = _interopRequireDefault(require("lodash/isPlainObject"));
37
38var _invariant = _interopRequireDefault(require("invariant"));
39
40var _identity = _interopRequireDefault(require("lodash/identity"));
41
42var _oors = require("oors");
43
44var _graphqlSubscriptions = require("graphql-subscriptions");
45
46var _merge = _interopRequireDefault(require("lodash/merge"));
47
48var _graphqlTools = require("graphql-tools");
49
50var _middleware = require("graphql-voyager/middleware");
51
52var _graphqlImport = require("graphql-import");
53
54var _graphqlBinding = require("graphql-binding");
55
56var _graphqlConstraintDirective = _interopRequireDefault(require("graphql-constraint-directive"));
57
58var _graphqlDepthLimit = _interopRequireDefault(require("graphql-depth-limit"));
59
60var _validators = require("oors-express/build/validators");
61
62var _resolvers = _interopRequireDefault(require("./graphql/resolvers"));
63
64var _modulesResolvers = _interopRequireDefault(require("./graphql/modulesResolvers"));
65
66var _LoadersMap = _interopRequireDefault(require("./libs/LoadersMap"));
67
68var decorators = _interopRequireWildcard(require("./decorators"));
69
70exports.decorators = decorators;
71Object.keys(decorators).forEach(function (key) {
72 if (key === "default" || key === "__esModule") return;
73 if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
74 Object.defineProperty(exports, key, {
75 enumerable: true,
76 get: function () {
77 return decorators[key];
78 }
79 });
80});
81
82var _Server = _interopRequireDefault(require("./libs/Server"));
83
84var _Resolvers = _interopRequireDefault(require("./libs/Resolvers"));
85
86function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
87
88function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
89
90function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
91
92function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
93
94function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
95
96const asyncGlob = (...args) => new Promise((resolve, reject) => {
97 (0, _glob.default)(...args, (err, files) => {
98 if (err) {
99 reject(err);
100 } else {
101 resolve(files);
102 }
103 });
104});
105
106class Gql extends _oors.Module {
107 constructor(..._args) {
108 super(..._args);
109 this.name = 'oors.graphql';
110 this.hooks = {
111 'oors.cache.load': ({
112 createPolicy
113 }) => {
114 createPolicy('graphqlResolvers');
115 }
116 };
117
118 this.teardown = () => this.server.stop();
119
120 this.extendContext = extender => {
121 (0, _invariant.default)(typeof extender === 'function' || (0, _isPlainObject.default)(extender), `Invalid context extender! Needs to be either a function or a an object that will get
122 assigned to the context.`);
123 this.contextExtenders.push(extender);
124 return this.contextExtenders;
125 };
126
127 this.bindSchema = (schema, options = {}) => new _graphqlBinding.Binding(_objectSpread({
128 schema
129 }, options));
130
131 this.addTypeDefs = typeDefs => {
132 this.typeDefs.push(typeDefs);
133 };
134
135 this.addDirectives = directives => {
136 Object.assign(this.directives, directives);
137 };
138
139 this.addResolvers = resolvers => {
140 if (this.schema) {
141 (0, _graphqlTools.addResolveFunctionsToSchema)({
142 schema: this.schema,
143 resolvers
144 });
145 } else {
146 (0, _merge.default)(this.resolvers, resolvers);
147 }
148 };
149
150 this.addResolverMiddleware = (matcher, middleware) => {
151 this.resolverMiddlewares.push({
152 matcher: typeof matcher === 'string' ? new RegExp(`^${matcher}$`) : matcher,
153 middleware
154 });
155 return this;
156 };
157
158 this.addLoader = (...args) => {
159 this.loaders.add(...args);
160 };
161
162 this.addLoaders = (...args) => {
163 this.loaders.multiAdd(...args);
164 };
165
166 this.addTypeDefsByPath = async filePath => {
167 this.addTypeDefs((await _fsExtra.default.readFile(filePath, 'utf8')));
168 };
169
170 this.importSchema = schemaPath => {
171 this.addTypeDefs((0, _graphqlImport.importSchema)(schemaPath));
172 };
173
174 this.loadFromDir = async dirPath => Promise.all([this.loadTypeDefsFromDir(dirPath), this.loadResolversFromDir(dirPath), this.loadDirectivesFromDir(dirPath)]);
175
176 this.loadTypeDefsFromDir = async dirPath => {
177 try {
178 // try to load /graphql/typeDefs/**/*.graphl
179 const typeDefsDirPath = _path.default.join(dirPath, 'typeDefs');
180
181 const stats = await _fsExtra.default.stat(typeDefsDirPath);
182
183 if (stats.isDirectory()) {
184 const files = await asyncGlob(_path.default.resolve(typeDefsDirPath, '**/*.graphql'));
185 await Promise.all(files.map(file => this.addTypeDefsByPath(file)));
186 }
187 } catch (_unused) {
188 // try to load /graphql/typeDefs.graphl
189 try {
190 await this.addTypeDefsByPath(_path.default.join(dirPath, 'typeDefs.graphql'));
191 } catch (_unused2) {}
192 }
193 };
194
195 this.loadResolversFromDir = async dirPath => {
196 try {
197 const resolvers = require(`${dirPath}/resolvers`);
198
199 if (resolvers.default) {
200 Object.assign(resolvers, resolvers.default);
201 delete resolvers.default;
202 }
203
204 this.addResolvers(resolvers);
205 } catch (err) {
206 const resolversPath = await asyncGlob(_path.default.resolve(`${dirPath}/resolvers`, '**/*.js'));
207 const resolvers = resolversPath.reduce((acc, resolverPath) => {
208 const resolverName = _path.default.relative(`${dirPath}/resolvers`, resolverPath).slice(0, -3).split(_path.default.sep).join('.');
209
210 (0, _set.default)(acc, resolverName, require(resolverPath).default);
211 return acc;
212 }, {});
213 this.addResolvers(resolvers);
214 }
215 };
216
217 this.loadDirectivesFromDir = async dirPath => {
218 try {
219 const directives = require(`${dirPath}/directives`);
220
221 if (directives.default) {
222 Object.assign(directives, directives.default);
223 delete directives.default;
224 }
225
226 this.addDirectives(directives);
227 } catch (err) {}
228 };
229
230 this.collectFromModule = async module => {
231 if (!module.getConfig('oors.graphql.autoload', true)) {
232 return;
233 }
234
235 if ((0, _has.default)(module, 'graphql')) {
236 this.addTypeDefs((0, _get.default)(module, 'graphql.typeDefs', ''));
237 this.addResolvers((0, _get.default)(module, 'graphql.resolvers', {}));
238
239 if ((0, _has.default)(module, 'graphql.typeDefsPath')) {
240 await this.addTypeDefsByPath((0, _get.default)(module, 'graphql.typeDefsPath'));
241 }
242 } else {
243 await this.loadFromDir(_path.default.resolve(_path.default.dirname(module.filePath), 'graphql'));
244 }
245 };
246
247 this.buildSchema = async () => {
248 const schema = (0, _graphqlTools.makeExecutableSchema)(this.getConfig('configureSchema', _identity.default)({
249 typeDefs: this.typeDefs,
250 resolvers: this.applyResolversMiddlewares(this.resolvers),
251 logger: {
252 log: err => {
253 this.emit('error', err);
254 }
255 },
256 allowUndefinedInResolve: false,
257 inheritResolversFromInterfaces: true,
258 schemaDirectives: this.directives
259 }));
260 const schemas = (await this.runHook('getSchema', () => {}, {
261 schema,
262 mergeSchemas: _graphqlTools.mergeSchemas,
263 makeExecutableSchema: _graphqlTools.makeExecutableSchema,
264 makeRemoteExecutableSchema: _graphqlTools.makeRemoteExecutableSchema
265 })).filter(s => s);
266 return schemas.length ? (0, _graphqlTools.mergeSchemas)({
267 schemas: [schema, ...schemas]
268 }) : schema;
269 };
270
271 this.buildServer = (options = {}) => {
272 const config = _objectSpread({
273 context: this.buildContext,
274 formatError: this.format('error'),
275 formatParams: this.format('params'),
276 formatResponse: this.format('response'),
277 schema: this.schema,
278 debug: true,
279 tracing: true,
280 cacheControl: true,
281 subscriptions: true,
282 introspection: true,
283 mocks: false,
284 persistedQueries: true,
285 validationRules: [(0, _graphqlDepthLimit.default)(this.getConfig('depthLimit.limit'), this.getConfig('depthLimit.options'), this.getConfig('depthLimit.callback'))],
286 costAnalysisConfig: this.getConfig('costAnalysis')
287 }, this.getConfig('serverOptions'), {}, options);
288
289 const server = new _Server.default(config);
290
291 if (config.subscriptions) {
292 server.installSubscriptionHandlers(this.deps['oors.express'].server);
293 }
294
295 return server;
296 };
297
298 this.buildContext = ({
299 req,
300 connection
301 } = {}) => {
302 const context = _objectSpread({}, this.gqlContext, {
303 loaders: this.loaders.build(),
304 req,
305 connection
306 }, req ? {
307 user: req.user
308 } : {}, {}, connection ? connection.context || {} : {});
309
310 this.contextExtenders.forEach(extender => extender(context));
311
312 context.execute = (source, options = {}) => this.execute(source, _objectSpread({}, options, {
313 context: () => _objectSpread({}, context, {}, options.context || {})
314 }));
315
316 return context;
317 };
318
319 this.execute = (source, options = {}) => {
320 const {
321 root,
322 context,
323 variables,
324 operation
325 } = _objectSpread({
326 root: undefined,
327 variables: {},
328 operation: undefined
329 }, options, {
330 context: typeof options.context === 'function' ? options.context(this.buildContext) : _objectSpread({}, this.buildContext(), {}, options.context || {})
331 });
332
333 return (0, _graphql.graphql)(this.schema, source, root, context, variables, operation);
334 };
335
336 this.format = type => {
337 (0, _invariant.default)(Array.isArray(this.formatters[type]), `Unknown formatter type - ${type}!`);
338 return arg => this.formatters[type].reduce((acc, formatter) => formatter(acc), arg);
339 };
340 }
341
342 initialize() {
343 this.typeDefs = [];
344 this.directives = {
345 constraint: _graphqlConstraintDirective.default
346 };
347 this.resolvers = _resolvers.default;
348 this.resolverMiddlewares = [];
349 this.pubsub = this.getConfig('pubsub', new _graphqlSubscriptions.PubSub());
350 this.gqlContext = {
351 pubsub: this.pubsub,
352 modules: this.manager
353 };
354 this.loaders = new _LoadersMap.default();
355 this.contextExtenders = [];
356 this.formatters = {
357 params: [],
358 error: [],
359 response: []
360 };
361 }
362
363 async setup() {
364 await this.loadDependencies(['oors.express']);
365 await this.runHook('load', this.collectFromModule, (0, _pick.default)(this, ['pubsub', 'addTypeDefs', 'addDirectives', 'addTypeDefsByPath', 'addResolvers', 'addResolverMiddleware', 'addLoader', 'loadFromDir']));
366 Object.assign(this.gqlContext, {
367 app: this.deps['oors.express'].app
368 });
369
370 if (this.getConfig('exposeModules')) {
371 this.addResolvers(_modulesResolvers.default);
372 await this.addTypeDefsByPath(_path.default.resolve(__dirname, './graphql/modulesTypeDefs.graphql'));
373 }
374
375 await this.runHook('buildContext', () => {}, {
376 context: this.gqlContext
377 });
378 this.schema = await this.buildSchema();
379 this.server = this.buildServer();
380 this.applyMiddlewares(this.server);
381 const binding = this.bindSchema(this.schema);
382 this.exportProperties(['extendContext', 'schema', 'server', 'loaders', 'addResolverMiddleware', 'addLoader', 'addLoaders', 'addResolvers', 'importSchema', 'bindSchema', 'binding', 'setupListen', 'pubsub', 'formatters', 'buildContext', 'execute']);
383 this.export({
384 context: this.gqlContext,
385 addSchemaResolvers: rootResolveFunction => (0, _graphqlTools.addSchemaLevelResolveFunction)(this.schema, rootResolveFunction),
386 addDirectivesResolvers: directivesResolvers => (0, _graphqlTools.attachDirectiveResolvers)(this.schema, directivesResolvers)
387 });
388 this.on('after:setup', () => {
389 Object.assign(this.gqlContext, {
390 binding
391 });
392 });
393 }
394
395 applyResolversMiddlewares(resolvers) {
396 if (!this.resolverMiddlewares.length) {
397 return resolvers;
398 }
399
400 return Object.keys(resolvers).reduce((resolversAcc, type) => _objectSpread({}, resolversAcc, {
401 [type]: Object.keys(resolvers[type]).reduce((typeAcc, field) => {
402 let resolver = resolvers[type][field];
403 const branch = `${type}.${field}`;
404 const middlewares = this.resolverMiddlewares.filter(({
405 matcher
406 }) => matcher.test(branch));
407
408 if (middlewares.length) {
409 resolver = [...middlewares].reverse().reduce((acc, {
410 middleware
411 }) => (...args) => middleware(...args, acc), resolver);
412 }
413
414 return _objectSpread({}, typeAcc, {
415 [field]: resolver
416 });
417 }, {})
418 }), {});
419 }
420
421 applyMiddlewares(server) {
422 this.deps['oors.express'].middlewares.insertBefore(this.getConfig('middlewarePivot'), this.getApolloServerMiddlewares(server), this.getVoyagerMiddleware());
423 } // eslint-disable-next-line class-methods-use-this
424
425
426 getApolloServerMiddlewares(server) {
427 return {
428 id: 'apolloServer',
429 apply: ({
430 app
431 }) => {
432 server.applyMiddleware({
433 app,
434 cors: false,
435 bodyParserConfig: false,
436 onHealthCheck: req => this.asyncEmit('healthCheck', req)
437 });
438 }
439 };
440 }
441
442 getVoyagerMiddleware() {
443 return {
444 id: 'voyager',
445 path: '/voyager',
446 factory: ({
447 endpointURL
448 }) => (0, _middleware.express)({
449 endpointUrl: endpointURL
450 }),
451 params: this.getConfig('voyager.params'),
452 enabled: this.getConfig('voyager.enabled')
453 };
454 }
455
456}
457
458exports.default = Gql;
459Gql.validateConfig = (0, _easevalidation.validate)(_easevalidation.validators.isSchema({
460 voyager: [_easevalidation.validators.isDefault({}), _easevalidation.validators.isSchema({
461 enabled: [_easevalidation.validators.isDefault(true), _easevalidation.validators.isBoolean()],
462 params: [_easevalidation.validators.isDefault({}), _easevalidation.validators.isSchema({
463 endpointURL: [_easevalidation.validators.isDefault('/graphql'), _easevalidation.validators.isString()]
464 })]
465 })],
466 middlewarePivot: [_easevalidation.validators.isDefault('isMethod'), (0, _validators.isMiddlewarePivot)()],
467 configureSchema: _easevalidation.validators.isAny(_easevalidation.validators.isFunction(), _easevalidation.validators.isUndefined()),
468 exposeModules: [_easevalidation.validators.isDefault(true), _easevalidation.validators.isBoolean()],
469 serverOptions: [_easevalidation.validators.isDefault({}), _easevalidation.validators.isObject()],
470 pubsub: _easevalidation.validators.isAny(_easevalidation.validators.isObject(), _easevalidation.validators.isUndefined()),
471 depthLimit: [_easevalidation.validators.isDefault({}), _easevalidation.validators.isSchema({
472 limit: [_easevalidation.validators.isDefault(10), _easevalidation.validators.isInteger()],
473 options: _easevalidation.validators.isAny(_easevalidation.validators.isSchema({
474 ignore: [_easevalidation.validators.isDefault([]), _easevalidation.validators.isArray()]
475 }), _easevalidation.validators.isUndefined()),
476 callback: _easevalidation.validators.isAny(_easevalidation.validators.isFunction(), _easevalidation.validators.isUndefined())
477 })],
478 costAnalysis: [_easevalidation.validators.isDefault({}), _easevalidation.validators.isSchema({
479 maximumCost: [_easevalidation.validators.isDefault(1000), _easevalidation.validators.isInteger(), _easevalidation.validators.isPositive()]
480 })]
481}));
\No newline at end of file