UNPKG

43.1 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.GQLBase = exports.PROPS = exports.SETTERS = exports.GETTERS = exports.AUTO_PROPS = exports.META_KEY = exports.REQ_DATA_KEY = exports.MODEL_KEY = undefined;
7
8var _defineProperty = require('babel-runtime/core-js/object/define-property');
9
10var _defineProperty2 = _interopRequireDefault(_defineProperty);
11
12var _promise = require('babel-runtime/core-js/promise');
13
14var _promise2 = _interopRequireDefault(_promise);
15
16var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator');
17
18var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);
19
20var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
21
22var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
23
24var _toStringTag = require('babel-runtime/core-js/symbol/to-string-tag');
25
26var _toStringTag2 = _interopRequireDefault(_toStringTag);
27
28var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
29
30var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
31
32var _keys = require('babel-runtime/core-js/object/keys');
33
34var _keys2 = _interopRequireDefault(_keys);
35
36var _symbol = require('babel-runtime/core-js/symbol');
37
38var _symbol2 = _interopRequireDefault(_symbol);
39
40var _for = require('babel-runtime/core-js/symbol/for');
41
42var _for2 = _interopRequireDefault(_for);
43
44exports.notDefined = notDefined;
45
46var _path = require('path');
47
48var _path2 = _interopRequireDefault(_path);
49
50var _fs = require('fs');
51
52var _fs2 = _interopRequireDefault(_fs);
53
54var _utils = require('./utils');
55
56var _neTypes = require('ne-types');
57
58var _SyntaxTree = require('./SyntaxTree');
59
60var _ModelProperties = require('./decorators/ModelProperties');
61
62var _graphql = require('graphql');
63
64var _IDLFileHandler = require('./IDLFileHandler');
65
66var _lodash = require('lodash');
67
68var _neTagFns = require('ne-tag-fns');
69
70var _AsyncFunctionExecutionError = require('./errors/AsyncFunctionExecutionError');
71
72var _AsyncFunctionExecutionError2 = _interopRequireDefault(_AsyncFunctionExecutionError);
73
74var _FunctionExecutionError = require('./errors/FunctionExecutionError');
75
76var _FunctionExecutionError2 = _interopRequireDefault(_FunctionExecutionError);
77
78var _AwaitingPromiseError = require('./errors/AwaitingPromiseError');
79
80var _AwaitingPromiseError2 = _interopRequireDefault(_AwaitingPromiseError);
81
82var _events = require('events');
83
84var _events2 = _interopRequireDefault(_events);
85
86function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
87
88/* Internal implementation to detect the existence of proxies. When present
89 * additional functionality is enabled. Proxies are native in Node >= 6 */
90const hasProxy = typeof global.Proxy !== 'undefined';
91
92/* Internal Symbol referring to real accessor to GQLBase model object */
93/** @namespace GQLBaseEnv */
94
95
96const _MODEL_KEY = (0, _for2.default)('data-model-contents-value');
97
98/* Internal Symbol referring to the static object containing a proxy handler */
99const _PROXY_HANDLER = (0, _for2.default)('internal-base-proxy-handler');
100
101/**
102 * Simple function to check if a supplied key matches a string of your
103 * choosing and that string is not a defined property on the instance
104 * passed to the check.
105 *
106 * @method GQLBaseEnv~notDefined
107 * @memberof GQLBaseEnv
108 * @since 2.5.0
109 *
110 * @param {string} keyToTest a String denoting the property you wish to test
111 * @param {mixed} keySupplied a value, coerced `toString()`, to compare to
112 * `keyToTest`
113 * @param {mixed} instance an object instance to check `hasOwnProperty` on for
114 * the `keyToTest` supplied.
115 * @return {Boolean} true if the property matches the supplied key and that
116 * property is not an ownedProperty of the instance supplied.
117 */
118function notDefined(keyToTest, keySupplied, instance) {
119 return new RegExp("^" + keyToTest + "$").test(keySupplied.toString()) && !instance.hasOwnProperty(keyToTest);
120}
121
122/**
123 * A `Symbol` used as a key to store the backing model data. Designed as a
124 * way to separate model data and GraphQL property accessors into logical bits.
125 *
126 * @type {Symbol}
127 * @memberof GQLBaseEnv
128 * @const
129 */
130const MODEL_KEY = exports.MODEL_KEY = (0, _for2.default)('data-model-contents-key');
131
132/**
133 * A `Symbol` used as a key to store the request data for an instance of the
134 * GQLBase object in question.
135 *
136 * @type {Symbol}
137 * @const
138 * @inner
139 * @memberof GQLBaseEnv
140 */
141const REQ_DATA_KEY = exports.REQ_DATA_KEY = (0, _for2.default)('request-data-object-key');
142
143/**
144 * A nameless Symbol for use as a key to the internal decorator storage
145 *
146 * @type {Symbol}
147 * @const
148 * @inner
149 * @memberof GQLBaseEnv
150 */
151const META_KEY = exports.META_KEY = (0, _symbol2.default)();
152
153/**
154 * A Symbol used to identify calls to @Properties for properties generated
155 * automatically upon instance creation.
156 *
157 * @type {Symbol}
158 * @const
159 * @inner
160 * @memberOf GQLBaseEnv
161 */
162const AUTO_PROPS = exports.AUTO_PROPS = (0, _for2.default)('auto-props');
163
164/**
165 * A Symbol used to identify calls to @Getters for properties generated
166 * via decorator. These are stored in <class>[META_KEY][GETTERS]
167 *
168 * @type {Symbol}
169 * @const
170 * @inner
171 * @memberOf GQLBaseEnv
172 */
173const GETTERS = exports.GETTERS = (0, _for2.default)('getters');
174
175/**
176 * A Symbol used to identify calls to @Setters for properties generated
177 * via decorator. These are stored in <class>[META_KEY][SETTERS]
178 *
179 * @type {Symbol}
180 * @const
181 * @inner
182 * @memberOf GQLBaseEnv
183 */
184const SETTERS = exports.SETTERS = (0, _for2.default)('setters');
185
186/**
187 * A Symbol used to identify calls to @Properties for properties generated
188 * via decorator. These are stored in <class>[META_KEY][PROPS]
189 *
190 * @type {Symbol}
191 * @const
192 * @inner
193 * @memberOf GQLBaseEnv
194 */
195const PROPS = exports.PROPS = (0, _for2.default)('props');
196
197/**
198 * All GraphQL Type objects used in this system are assumed to have extended
199 * from this class. An instance of this class can be used to wrap an existing
200 * structure if you have one.
201 *
202 * @class GQLBase
203 */
204let GQLBase = exports.GQLBase = class GQLBase extends _events2.default {
205
206 /**
207 * Request data is passed to this object when constructed. Typically these
208 * objects, and their children, are instantiated by its own static MUTATORS
209 * and RESOLVERS. They should contain request specific state if any is to
210 * be shared.
211 *
212 * These can be considered request specific controllers for the object in
213 * question. The base class takes a single object which should contain all
214 * the HTTP/S request data and the graphQLParams is provided as the object
215 * { query, variables, operationName, raw }.
216 *
217 * When used with express-graphql, the requestData object has the format
218 * { req, res, gql } where
219 * • req is an Express 4.x request object
220 * • res is an Express 4.x response object
221 * • gql is the graphQLParams object in the format of
222 * { query, variables, operationName, raw }
223 * See https://github.com/graphql/express-graphql for more info
224 *
225 * @memberof GQLBase
226 * @method ⎆⠀constructor
227 * @constructor
228 *
229 * @param {mixed} modelData this, typically an object, although anything
230 * really is supported, represents the model data for our GraphQL object
231 * instance.
232 * @param {Object} requestData see description above
233 */
234 constructor(modelData = {}, requestData = null, options = { autoProps: true }) {
235 super();
236
237 const Class = this.constructor;
238 const tree = _SyntaxTree.SyntaxTree.from(Class.SCHEMA);
239 const outline = tree && tree.outline || null;
240
241 if (!outline) {
242 throw new _FunctionExecutionError2.default(new Error(_neTagFns.dedent`
243 The SDL is unparsable. Please check your SCHEMA and make sure
244 it is valid GraphQL SDL/IDL. Your SCHEMA is defined as:
245
246 ${this.SCHEMA}
247 `));
248 }
249
250 if (outline && !(Class.name in outline)) {
251 throw new _FunctionExecutionError2.default(new Error(_neTagFns.dedent`
252 The class name "${Class.name}" does not match any of the types,
253 enums, scalars, unions or interfaces defined in the SCHEMA for
254 this class (${(0, _keys2.default)(outline)}).
255
256 \x1b[1mIn most clases this is because your class name and SCHEMA
257 type do not match.\x1b[0m
258 `));
259 }
260
261 GQLBase.setupModel(this);
262 this.setModel(modelData);
263 this.requestData = requestData || {};
264 this.fileHandler = new _IDLFileHandler.IDLFileHandler(this.constructor);
265
266 if (options && !!options.autoProps !== false) {
267 this.applyAutoProps();
268 }
269
270 // @ComputedType
271 return hasProxy ? new Proxy(this, GQLBase[_PROXY_HANDLER]) : this;
272 }
273
274 /**
275 * Since reading the Schema for a given GraphQL Lattice type or
276 * interface is simple enough, we should be able to automatically
277 * apply one to one GraphQL:Model properties.
278 *
279 * @instance
280 * @method ⌾⠀applyAutoProps
281 * @memberof GQLBase
282 */
283 applyAutoProps() {
284 if (!this.constructor.SCHEMA || !this.constructor.SCHEMA.length) {
285 _utils.LatticeLogs.warn(_utils.joinLines`
286 There is no SCHEMA for ${this.constructor.name}!! This will likely
287 end in an error. Proceed with caution. Skipping \`applyAutoProps\`
288 `);
289 return;
290 }
291
292 // Individual property getters do not need to be auto-created for enum
293 // types. Potentially do some checks for Interfaces and Unions as well
294 if (this.constructor.GQL_TYPE === _graphql.GraphQLEnumType) {
295 return;
296 }
297
298 let Class = this.constructor;
299 let tree = _SyntaxTree.SyntaxTree.from(Class.SCHEMA);
300 let outline = tree ? tree.outline : {};
301 let props = [];
302
303 // $FlowFixMe
304 for (let propName of (0, _keys2.default)(outline[Class.name])) {
305 // $FlowFixMe
306 let desc = (0, _getOwnPropertyDescriptor2.default)(Class.prototype, propName);
307 let hasCustomImpl = !!(
308 // We have a descriptor for the property name
309 desc && (
310 // We have a getter function defined
311 typeof desc.get !== 'undefined' ||
312 // ...or we have a function, async or not, defined
313 typeof desc.value === 'function'));
314
315 // Only create auto-props for non custom implementations
316 if (!hasCustomImpl) {
317 props.push(propName);
318 }
319 }
320
321 if (props.length) {
322 _utils.LatticeLogs.info(`Creating auto-props for [${Class.name}]: `, props);
323 try {
324 (0, _ModelProperties.Properties)(...props)(Class, [AUTO_PROPS]);
325 } catch (error) {
326 let parsed = /Cannot redefine property: (\w+)/.exec(error.message);
327 if (parsed) {
328 _utils.LatticeLogs.warn(`Skipping auto-prop '${Class.name}.${parsed[1]}'`);
329 } else {
330 _utils.LatticeLogs.error(`Failed to apply auto-properties\nReason: `);
331 _utils.LatticeLogs.error(error);
332 }
333 }
334 }
335 }
336
337 /**
338 * Getter for the internally stored model data. The contents of this
339 * object are abstracted away behind a `Symbol` key to prevent collision
340 * between the underlying model and any GraphQL Object Definition properties.
341 *
342 * @instance
343 * @memberof GQLBase
344 * @method ⌾⠀getModel
345 * @since 2.5
346 *
347 * @param {Object} value any object you wish to use as a data store
348 */
349 getModel() {
350 // @ComputedType
351 return this[MODEL_KEY];
352 }
353
354 /**
355 * Setter for the internally stored model data. The contents of this
356 * object are abstracted away behind a `Symbol` key to prevent collision
357 * between the underlying model and any GraphQL Object Definition properties.
358 *
359 * @instance
360 * @memberof GQLBase
361 * @method ⌾⠀setModel
362 * @since 2.5
363 *
364 * @param {Object} value any object you wish to use as a data store
365 */
366 setModel(value) {
367 // @ComputedType
368 this[MODEL_KEY] = value;
369 return this;
370 }
371
372 /**
373 * Uses `_.merge()` to modify the internal backing data store for the
374 * object instance. This is a shortcut for
375 * `_.merge()(instance[MODEL_KEY], ...extensions)`
376 *
377 * @instance
378 * @memberof GQLBase
379 * @method ⌾⠀extendModel
380 * @since 2.5
381 *
382 * @param {mixed} extensions n-number of valid `_.merge()` parameters
383 * @return {GQLBase} this is returned
384 */
385 extendModel(...extensions) {
386 // $FlowFixMe
387 (0, _lodash.merge)(this[MODEL_KEY], ...extensions);
388 return this;
389 }
390
391 /**
392 * A getter that retrieves the inner request data object. When used with
393 * GQLExpressMiddleware, this is an object matching {req, res, gql}.
394 *
395 * @instance
396 * @memberof GQLBase
397 * @method ⬇︎⠀requestData
398 *
399 * @return {Object} an object, usually matching { req, res, gql }
400 */
401 get requestData() {
402 // @ComputedType
403 return this[REQ_DATA_KEY];
404 }
405
406 /**
407 * A setter that assigns a value to the inner request data object. When
408 * used with GQLExpressMiddleware, this is an object matching {req, res, gql}.
409 *
410 * @instance
411 * @memberof GQLBase
412 * @method ⬆︎⠀requestData
413 *
414 * @param {Object} value an object, usually matching { req, res, gql }
415 */
416 set requestData(value) {
417 // @ComputedType
418 this[REQ_DATA_KEY] = value;
419 }
420
421 /**
422 * Returns the `constructor` name. If invoked as the context, or `this`,
423 * object of the `toString` method of `Object`'s `prototype`, the resulting
424 * value will be `[object MyClass]`, given an instance of `MyClass`
425 *
426 * @method ⌾⠀[Symbol.toStringTag]
427 * @memberof ModuleParser
428 *
429 * @return {string} the name of the class this is an instance of
430 * @ComputedType
431 */
432 get [_toStringTag2.default]() {
433 return this.constructor.name;
434 }
435
436 /**
437 * Properties defined for GraphQL types in Lattice can be defined as
438 * a getter, a function or an async function. In the case of standard
439 * functions, if they return a promise they will be handled as though
440 * they were async
441 *
442 * Given the variety of things a GraphQL type can actually be, obtaining
443 * its value can annoying. This method tends to lessen that boilerplate.
444 * Errors raised will be thrown.
445 *
446 * @instance
447 * @memberof GQLBase
448 * @method ⌾⠀getProp
449 *
450 * @param {string|Symbol} propName the name of the property in question
451 * @param {boolean} bindGetters true, by default, if the `get` or
452 * `initializer` descriptor values should be bound to the current instance
453 * or an object of the programmers choice before returning
454 * @param {mixed} bindTo the `this` object to use for binding when
455 * `bindGetters` is set to true.
456 * @return {mixed} the value of the `propName` as a Function or something
457 * else when the requested property name exists
458 *
459 * @throws {Error} errors raised in awaiting results will be thrown
460 */
461 getProp(propName, bindGetters = true, bindTo) {
462 // $FlowFixMe
463 let proto = (0, _getPrototypeOf2.default)(this);
464 let descriptor = (0, _getOwnPropertyDescriptor2.default)(proto, propName);
465 let result;
466
467 if (!descriptor) {
468 return null;
469 }
470
471 if (descriptor) {
472 if (descriptor.initializer || descriptor.get) {
473 let what = descriptor.initializer || descriptor.get;
474
475 if (bindGetters) {
476 result = what.bind(bindTo || this);
477 } else {
478 result = what;
479 }
480 } else if (descriptor.value) {
481 result = descriptor.value;
482 }
483 }
484
485 return result;
486 }
487
488 /**
489 * Properties defined for GraphQL types in Lattice can be defined as
490 * a getter, a function or an async function. In the case of standard
491 * functions, if they return a promise they will be handled as though
492 * they were async. In addition to fetching the property, or field
493 * resolver, its resulting function or getter will be invoked.
494 *
495 * Given the variety of things a GraphQL type can actually be, obtaining
496 * its value can annoying. This method tends to lessen that boilerplate.
497 * Errors raised will be thrown.
498 *
499 * @instance
500 * @memberof GQLBase
501 * @method ⌾⠀callProp
502 *
503 * @param {string} propName the name of the property in question
504 * @param {Array<mixed>} args the arguments array that will be passed
505 * to `.apply()` should the property evaluate to a `function`
506 * @return {mixed} the return value of any resulting function or
507 * value returned by a getter; wrapped in a promise as all async
508 * functions do.
509 *
510 * @throws {Error} errors raised in awaiting results will be thrown
511 */
512 callProp(propName, ...args) {
513 var _this = this;
514
515 return (0, _asyncToGenerator3.default)(function* () {
516 // $FlowFixMe
517 let prop = _this.getProp(propName, ...args);
518 let result;
519
520 if (prop && (0, _neTypes.typeOf)(prop) === 'AsyncFunction') {
521 try {
522 result = yield prop.apply(_this, args);
523 } catch (error) {
524 throw new _AsyncFunctionExecutionError2.default(error, prop, args, result);
525 }
526 } else if (prop && (0, _neTypes.typeOf)(prop) === Function.name) {
527 try {
528 result = prop.apply(_this, args);
529 } catch (error) {
530 throw new _FunctionExecutionError2.default(error, prop, args, result);
531 }
532
533 if ((0, _neTypes.typeOf)(result) === _promise2.default.name) {
534 try {
535 result = yield result;
536 } catch (error) {
537 throw new _AwaitingPromiseError2.default(error).setPromise(result);
538 }
539 }
540 }
541
542 return result;
543 })();
544 }
545
546 /**
547 * A pass-thru method to the static function of the same name. The
548 * difference being that if `requestData` is not specified, the
549 * `requestData` object from this instance will be used to build the
550 * resolvers in question.
551 *
552 * @instance
553 * @method ⌾⠀getResolver
554 * @memberof GQLBase
555 *
556 * @param {string} resolverName the name of the resolver as a string
557 * @param {Object} requestData the requestData used to build the
558 * resolver methods from which to choose
559 * @return {Function} returns either a `function` representing the
560 * resolver requested or null if there wasn't one to be found
561 */
562 getResolver(resolverName, requestData) {
563 var _this2 = this;
564
565 return (0, _asyncToGenerator3.default)(function* () {
566 return yield _this2.constructor.getResolver(resolverName, requestData || _this2.requestData);
567 })();
568 }
569
570 /**
571 * Resolvers are created in a number of different ways. OOP design
572 * dictates that instances of a created class will handle field
573 * resolvers, but query, mutation and subscription resolvers are
574 * typically what creates these instances.
575 *
576 * Since a resolver can be created using `@mutator/@subscriptor/@resolver`
577 * or via method on a object returned from `RESOLVERS()`, `MUTATORS()` or
578 * `SUBSCRIPTIONS()`, there should be an easy to use way to fetch a
579 * resolver by name; if for nothing else, code reuse.
580 *
581 * Pass the name of the resolver to the function and optionally pass a
582 * requestData object. The `getMergedRoot()` method will build an object
583 * containing all the root resolvers for the type, bound to the supplied
584 * `requestData` object. It is from this object that `resolverName` will
585 * be used to fetch the function in question. If one exists, it will be
586 * returned, ready for use. Otherwise, null will be your answer.
587 *
588 *
589 * @static
590 * @method ⌾⠀getResolver
591 * @memberof GQLBase
592 *
593 * @param {string} resolverName the name of the resolver as a string
594 * @param {Object} requestData the requestData used to build the
595 * resolver methods from which to choose
596 * @return {Function} returns either a `function` representing the
597 * resolver requested or null if there wasn't one to be found
598 */
599 static getResolver(resolverName, requestData) {
600 var _this3 = this;
601
602 return (0, _asyncToGenerator3.default)(function* () {
603 const reqData = requestData || null;
604 const rootObj = yield _this3.getMergedRoot(reqData);
605
606 return rootObj[resolverName] || null;
607 })();
608 }
609
610 /**
611 * The static version of getProp reads into the prototype to find the field
612 * that is desired. If the field is either a getter or a initializer (see
613 * class properties descriptors), then the option to bind that to either the
614 * prototype object or one of your choosing is available.
615 *
616 * @memberof GQLBase
617 * @method ⌾⠀getProp
618 * @static
619 *
620 * @param {string|Symbol} propName a string or Symbol denoting the name of
621 * the property or field you desire
622 * @param {boolean} bindGetters true if a resulting `getter` or `initializer`
623 * should be bound to the prototype or other object
624 * @param {mixed} bindTo the object to which to bind the `getter` or
625 * `initializer` functions to if other than the class prototype.
626 * @return {mixed} a `Function` or other mixed value making up the property
627 * name requested
628 */
629 static getProp(propName, bindGetters = false, bindTo) {
630 let descriptor = (0, _getOwnPropertyDescriptor2.default)(this.prototype, propName);
631
632 if (descriptor) {
633 if (descriptor.get || descriptor.initializer) {
634 let what = descriptor.initializer || descriptor.get;
635
636 if (bindGetters) {
637 bindTo = bindTo || this.prototype;
638
639 return what.bind(bindTo);
640 } else {
641 return what;
642 }
643 } else {
644 return descriptor.value;
645 }
646 } else {
647 return null;
648 }
649 }
650
651 /**
652 * Until such time as the reference implementation of Facebook's GraphQL
653 * SDL AST parser supports comments, or until we take advantage of Apollo's
654 * AST parser, this is how comments will be applied to a built schema.
655 *
656 * Several constants are defined on the GQLBase object itself, and thereby
657 * all its subclasses. They pertain to how to define description fields
658 * for various parts of your GQL implementation.
659 *
660 * ```
661 * // To define a description on the top level class
662 * [this.DOC_CLASS]: string
663 *
664 * // To define a description on a field (getter, function or async function)
665 * [this.DOC_FIELDS]: {
666 * fieldName: string
667 * }
668 *
669 * // To define a description on a query, mutation or subscription field
670 * [this.DOC_QUERIES || this.DOC_MUTATORS || this.DOC_SUBSCRIPTIONS]: {
671 * fieldName: string
672 * }
673 * ```
674 *
675 * To make writing code easier, the `joinLines()` template function is
676 * available so your source code can look nice and neat and your descriptions
677 * won't get annoying line breaks and spaces as part of that process.
678 *
679 * @static
680 * @memberof GQLBase
681 * @method apiDocs
682 *
683 * @return {Object} an object with various keys and values denoting
684 * description fields that should be applied to the final schema object
685 */
686 static apiDocs() {
687 return {
688 [this.DOC_CLASS]: _utils.joinLines`
689 GQLBase class implementation. GQLBase is the root class used in
690 graphql-lattice to describe a GraphQLObjectType. If you are reading
691 this, the person using lattice failed to provide documentation for
692 their type. :)
693 `,
694
695 [this.DOC_QUERY]: _utils.joinLines`
696 ## Welcome to GraphQL Lattice
697 **Query**
698
699 You will want to define a \`DOC_QUERY\` apiDoc comment with something
700 more meaningful to your particular Schema here.
701 `,
702
703 [this.DOC_MUTATION]: _utils.joinLines`
704 ## Welcome to GraphQL Lattice
705 **Mutation**
706
707 You will want to define a \`DOC_MUTATION\` apiDoc comment with
708 something more meaningful to your particular Schema here.
709 `,
710
711 [this.DOC_SUBSCRIPTION]: _utils.joinLines`
712 ## Welcome to GraphQL Lattice
713 **Subscription**
714
715 You will want to define a \`DOC_SUBSCRIPTION\` apiDoc comment with
716 something more meaningful to your particular Schema here.
717 `,
718
719 [this.DOC_FIELDS]: {
720 // fieldName: `fieldDescription`,
721 },
722
723 [this.DOC_QUERIES]: {
724 // queryName: `queryDescription`,
725 },
726
727 [this.DOC_MUTATORS]: {
728 // mutatorName: `mutatorDescription`
729 },
730
731 [this.DOC_SUBSCRIPTIONS]: {
732 // subscriptionName: `subscriptionDescription`
733 }
734 };
735 }
736
737 /**
738 * Defined in a base class, this getter should return either a String
739 * detailing the full IDL schema of a GraphQL handler or one of two
740 * types of Symbols.
741 *
742 * The first Symbol type is the constant `ADJACENT_FILE`. If this Symbol is
743 * returned, the system assumes that next to the source file in question is
744 * a file of the same name with a .graphql extension. This file should be
745 * made of the GraphQL IDL schema definitions for the object types being
746 * created.
747 *
748 * Example:
749 * ```js
750 * static get SCHEMA(): string | Symbol {
751 * return GQLBase.ADJACENT_FILE
752 * }
753 * ```
754 *
755 * The primary advantage of this approach is allowing an outside editor that
756 * provides syntax highlighting rather than returning a string from the
757 * SCHEMA getter.
758 *
759 * Alternatively, the static method IDLFilePath can be used to point to an
760 * alternate location where the GraphQL IDL file resides. The extension can
761 * also be changed from .graphql to something else if need be using this
762 * method.
763 *
764 * Example:
765 * ```js
766 * static get SCHEMA(): string | Symbol {
767 * return GQLBase.IDLFilePath('/path/to/file', '.idl')
768 * }
769 * ```
770 *
771 * @instance
772 * @memberof GQLBase
773 * @method ⬇︎⠀SCHEMA
774 * @readonly
775 * @static
776 *
777 * @return {string|Symbol} a valid IDL string or one of the Symbols
778 * described above.
779 *
780 * @see {@link GQLBase#ADJACENT_FILE}
781 * @see {@link GQLBase#IDLFilePath}
782 */
783 static get SCHEMA() {
784 return '';
785 }
786
787 /**
788 * This method should return a promise that resolves to an object of
789 * functions matching the names of the mutation operations. These are to be
790 * injected into the root object when used by `GQLExpressMiddleware`.
791 *
792 * @instance
793 * @memberof GQLBase
794 * @method ⌾⠀MUTATORS
795 * @readonly
796 * @static
797 *
798 * @param {Object} requestData typically an object containing three
799 * properties; {req, res, gql}
800 * @return {Promise} a promise that resolves to an object; see above for more
801 * information.
802 */
803 static MUTATORS(requestData) {
804 return (0, _asyncToGenerator3.default)(function* () {
805 // define in base class
806 return {};
807 })();
808 }
809
810 /**
811 * This method should return a promise that resolves to an object of
812 * functions matching the names of the query operations. These are to be
813 * injected into the root object when used by `GQLExpressMiddleware`.
814 *
815 * @instance
816 * @memberof GQLBase
817 * @method ⌾⠀RESOLVERS
818 * @readonly
819 * @static
820 *
821 * @param {Object} requestData typically an object containing three
822 * properties; {req, res, gql}
823 * @return {Promise} a promise that resolves to an object; see above for more
824 * information.
825 */
826 static RESOLVERS(requestData) {
827 return (0, _asyncToGenerator3.default)(function* () {
828 // define in base class
829 return {};
830 })();
831 }
832
833 /**
834 * @see {@link GQLBase#SCHEMA}
835 *
836 * @memberof GQLBase
837 * @method ⬇︎⠀ADJACENT_FILE
838 * @static
839 * @const
840 *
841 * @return {Symbol} the Symbol, when returned from SCHEMA, causes
842 * the logic to load an IDL Schema from an associated file with a .graphql
843 * extension and bearing the same name.
844 */
845 static get ADJACENT_FILE() {
846 return (0, _for2.default)('.graphql file located adjacent to source');
847 }
848
849 /**
850 * Determines the default type targeted by this GQLBase class. Any
851 * type will technically be valid but only will trigger special behavior
852 *
853 * @memberof GQLBase
854 * @method ⬇︎⠀GQL_TYPE
855 * @static
856 * @const
857 *
858 * @return {Function} a type, such as `GraphQLObjectType` or
859 * `GraphQLInterfaceType`
860 */
861 static get GQL_TYPE() {
862 return _graphql.GraphQLObjectType;
863 }
864
865 /**
866 * Creates an appropriate Symbol crafted with the right data for use by
867 * the IDLFileHandler class below.
868 *
869 * @static
870 * @memberof GQLBase
871 * @method ⌾⠀IDLFilePath
872 *
873 * @param {string} path a path to the IDL containing file
874 * @param {string} [extension='.graphql'] an extension, including the
875 * prefixed period, that will be added to the supplied path should it not
876 * already exist.
877 * @return Symbol
878 *
879 * @see {@link GQLBase#SCHEMA}
880 */
881 static IDLFilePath(path, extension = '.graphql') {
882 return (0, _for2.default)(`Path ${path} Extension ${extension}`);
883 }
884
885 /**
886 * A file handler for fetching the IDL schema string from the file system
887 * for those `GQLBase` extended classes that have indicated to do so by
888 * returning a `Symbol` for their `SCHEMA` property.
889 *
890 * @static
891 * @memberof GQLBase
892 * @method ⬇︎⠀handler
893 *
894 * @return {IDLFileHandler} instance of IDLFileHandler, created if one does
895 * not already exist, for fetching the contents from disk.
896 */
897 static get handler() {
898 const key = (0, _for2.default)(`${_IDLFileHandler.IDLFileHandler.name}.${this.name}`);
899
900 // @ComputedType
901 if (!this[key]) {
902 // @ComputedType
903 this[key] = new _IDLFileHandler.IDLFileHandler(this);
904 }
905
906 // @ComputedType
907 return this[key];
908 }
909
910 /**
911 * Returns the module object where your class is created. This needs to be
912 * defined on your class, as a static getter, in the FILE where you are
913 * defining your Class definition.
914 *
915 * @static
916 * @memberof GQLBase
917 * @method ⬇︎⠀module
918 * @const
919 *
920 * @return {Object} the reference to the module object defined and injected
921 * by node.js' module loading system.
922 *
923 * @see https://nodejs.org/api/modules.html
924 */
925 static get module() {
926 return module;
927 }
928
929 /**
930 * The internal data model has some custom `EventEmitter` code wrapped
931 * it here. When the data model is set via `setModel` or by accessing it
932 * via `instance[MODEL_KEY]`, an event `EVENT_MODEL_SET` is emitted. Any
933 * listener listening for this event receives an object with two keys
934 * ```
935 * {
936 * model: The actual model being set; changes are persisted
937 * instance: The GQLBase instance the model is associated with
938 * }
939 * ```
940 *
941 * Subsequently, the events `EVENT_MODEL_PROP_CHANGE` and
942 * `EVENT_MODEL_PROP_DELETE` can be listened to if your version of node
943 * supports Proxy objects. They allow you to be notified whenever your
944 * model has a property changed or deleted, respectively.
945 *
946 * The callback for `change` receives an object with four properties
947 * ```
948 * {
949 * model: The model object the value is being changed on
950 * old: The old value being replaced; undefined if it is the first time
951 * key: The property key for the value being changed
952 * value: The new value being set
953 * }
954 * ```
955 *
956 * The callback for `delete` receives an object with four properties
957 * ```
958 * {
959 * model: The model object the value is deleted from
960 * key: The property key for the deleted value
961 * deleted: The deleted value
962 * }
963 * ```
964 *
965 * @static
966 * @memberof GQLBase
967 * @method ⌾⠀setupModel
968 *
969 * @param {GQLBase} instance typically `this` as passed in from a call in
970 * the constructor
971 */
972 static setupModel(instance) {
973 const changeHandler = {
974 /**
975 * Proxy set() handler. This is where the change events are fired from
976 *
977 * @method GQLBase~set
978 * @param {Object} target the `GQLBase` model object
979 * @param {string} key the property name
980 * @param {mixed} value the new property value
981 */
982 set(target, key, value) {
983 const old = target[key];
984
985 target[key] = value;
986 instance.emit(GQLBase.EVENT_MODEL_PROP_CHANGE, {
987 model: target,
988 old,
989 key,
990 value
991 });
992 },
993
994 /**
995 * Proxy deleteProperty() handler. This is where the delete property
996 * events are fired from
997 *
998 * @method GQLBase~deleteProperty
999 * @param {Object} target the `GQLBase` model object
1000 * @param {string} key the property name
1001 */
1002 deleteProperty(target, key) {
1003 const deleted = target[key];
1004
1005 delete target[key];
1006 instance.emit(GQLBase.EVENT_MODEL_PROP_DELETE, {
1007 model: target,
1008 key,
1009 deleted
1010 });
1011 }
1012 };
1013
1014 /**
1015 * 'Publicly' the Symbol for accessing the `GQLBase` model is `MODEL_KEY`.
1016 * In truth it is stored under a Symbol defined in `setupModel` and
1017 * referred to as `_MODEL_KEY` in this code. This is done so a getter and
1018 * setter can be wrapped around the usage of the instance's data model.
1019 *
1020 * When being read, if `Proxy` exists in the node environment and if there
1021 * are any registered `EVENT_MODEL_PROP_CHANGE` or `EVENT_MODEL_PROP_DELETE`
1022 * events, then the returned model is a Proxy around the real model that
1023 * allows us to capture the changes and deletion of keys
1024 *
1025 * When being assigned, the event `EVENT_MODEL_WILL_BE_SET` and the event
1026 * `EVENT_MODEL_HAS_BEEN_SET` are emitted to allow listeners to modify and
1027 * see the final data around the setting of a model object. Both events
1028 * receive an object with two keys
1029 *
1030 * ```
1031 * {
1032 * model: The object being or having been set
1033 * instance: The GQLBase instance receiving the model
1034 * }
1035 * ```
1036 */
1037 (0, _defineProperty2.default)(instance, MODEL_KEY, {
1038 get: function () {
1039 let model = this[_MODEL_KEY];
1040 let hasListeners = this.listenerCount(GQLBase.EVENT_MODEL_PROP_CHANGE) + this.listenerCount(GQLBase.EVENT_MODEL_PROP_DELETE);
1041
1042 if (hasProxy && hasListeners) {
1043 model = new Proxy(model, changeHandler);
1044 }
1045
1046 return model;
1047 },
1048
1049 set: function (model) {
1050 const instance = this;
1051
1052 this.emit(GQLBase.EVENT_MODEL_WILL_BE_SET, { model, instance });
1053 instance[_MODEL_KEY] = model;
1054 this.emit(GQLBase.EVENT_MODEL_HAS_BEEN_SET, { model, instance });
1055 }
1056 });
1057 }
1058
1059 /**
1060 * If ES6 Proxies are supported in your execution environment, all GQLBase
1061 * extended classes are also proxies. By default the internal proxy handler
1062 * provides backwards compatibility with the removal of the default getters
1063 * and setters for the 'model' property as long as you do not define a
1064 * top level 'model' property of your own.
1065 *
1066 * @method ⬇︎⠀[_PROXY_HANDLER]
1067 * @memberof GQLBase
1068 * @static
1069 * @const
1070 * @since 2.5.0
1071 *
1072 * @type {Object}
1073 * @ComputedType
1074 */
1075 static get [_PROXY_HANDLER]() {
1076 return {
1077 get(target, key, lastResult) {
1078 const model = target[_MODEL_KEY];
1079
1080 // Allow backwards compatibility for 'model' property if one is not
1081 // explicitly defined on your instance.
1082 if (notDefined('model', key, target)) {
1083 // Be sure to use the public MODEL_KEY to ensure events fire
1084 return target[MODEL_KEY];
1085 }
1086
1087 return target[key];
1088 }
1089 };
1090 }
1091
1092 /**
1093 * Applies the same logic as {@link #[Symbol.toStringTag]} but on a static
1094 * scale. So, if you perform `Object.prototype.toString.call(MyClass)`
1095 * the result would be `[object MyClass]`.
1096 *
1097 * @method ⌾⠀[Symbol.toStringTag]
1098 * @memberof ModuleParser
1099 * @static
1100 *
1101 * @return {string} the name of this class
1102 * @ComputedType
1103 */
1104 static get [_toStringTag2.default]() {
1105 return this.name;
1106 }
1107
1108 /**
1109 * A constant used to register an event listener for when the internal
1110 * model object is assigned a new value. This event fires before the model
1111 * is set. Changes to the model value at this point will affect the contents
1112 * before the value assignment takes place.
1113 *
1114 * @static
1115 * @memberof GQLBase
1116 * @method ⬇︎⠀EVENT_MODEL_WILL_BE_SET
1117 * @const
1118 *
1119 * @type {string}
1120 */
1121 static get EVENT_MODEL_WILL_BE_SET() {
1122 return 'E: Int. model will be set';
1123 }
1124
1125 /**
1126 * A constant used to register an event listener for when the internal
1127 * model object is assigned a new value. This event fires after the model
1128 * is set.
1129 *
1130 * @static
1131 * @memberof GQLBase
1132 * @method ⬇︎⠀EVENT_MODEL_HAS_BEEN_SET
1133 * @const
1134 *
1135 * @type {string}
1136 */
1137 static get EVENT_MODEL_HAS_BEEN_SET() {
1138 return 'E: Int. model has been set';
1139 }
1140
1141 /**
1142 * A constant used to register an event listener for when a property of the
1143 * internal model object is set to a new or intial value.
1144 *
1145 * @static
1146 * @memberof GQLBase
1147 * @method ⬇︎⠀EVENT_MODEL_PROP_CHANGE
1148 * @const
1149 *
1150 * @type {string}
1151 */
1152 static get EVENT_MODEL_PROP_CHANGE() {
1153 return 'E: Int. model prop changed';
1154 }
1155
1156 /**
1157 * A constant used to register an event listener for when a property of the
1158 * internal model object has been deleted. This event fires after the value
1159 * has been deleted.
1160 *
1161 * @static
1162 * @memberof GQLBase
1163 * @method ⬇︎⠀EVENT_MODEL_PROP_DELETE
1164 * @const
1165 *
1166 * @type {string}
1167 */
1168 static get EVENT_MODEL_PROP_DELETE() {
1169 return 'E: Int. model prop deleted';
1170 }
1171
1172 /**
1173 * A constant key used to identify a comment for a class description
1174 *
1175 * @static
1176 * @memberof GQLBase
1177 * @method ⬇︎⠀DOC_CLASS
1178 * @const
1179 *
1180 * @type {string}
1181 */
1182 static get DOC_CLASS() {
1183 return 'class';
1184 }
1185
1186 /**
1187 * A constant key used to identify a comment for a type field description
1188 *
1189 * @static
1190 * @memberof GQLBase
1191 * @method ⬇︎⠀DOC_FIELDS
1192 * @const
1193 *
1194 * @type {string}
1195 */
1196 static get DOC_FIELDS() {
1197 return 'fields';
1198 }
1199
1200 /**
1201 * A constant key used to identify a comment for the top level query
1202 * description
1203 *
1204 * @static
1205 * @memberof GQLBase
1206 * @method ⬇︎⠀DOC_QUERY
1207 * @const
1208 *
1209 * @type {string}
1210 */
1211 static get DOC_QUERY() {
1212 return 'query';
1213 }
1214
1215 /**
1216 * A constant key used to identify a comment for a query description
1217 *
1218 * @static
1219 * @memberof GQLBase
1220 * @method ⬇︎⠀DOC_QUERIES
1221 * @const
1222 *
1223 * @type {string}
1224 */
1225 static get DOC_QUERIES() {
1226 return 'queries';
1227 }
1228
1229 /**
1230 * A constant key used to identify a comment for the top level mutation
1231 * description
1232 *
1233 * @static
1234 * @memberof GQLBase
1235 * @method ⬇︎⠀DOC_MUTATION
1236 * @const
1237 *
1238 * @type {string}
1239 */
1240 static get DOC_MUTATION() {
1241 return 'mutation';
1242 }
1243
1244 /**
1245 * A constant key used to identify a comment for a mutator description
1246 *
1247 * @static
1248 * @memberof GQLBase
1249 * @method ⬇︎⠀DOC_MUTATORS
1250 * @const
1251 * @deprecated Use `DOC_MUTATIONS` instead
1252 *
1253 * @type {string}
1254 */
1255 static get DOC_MUTATORS() {
1256 return 'mutators';
1257 }
1258
1259 /**
1260 * A constant key used to identify a comment for a mutator description
1261 *
1262 * @static
1263 * @memberof GQLBase
1264 * @method ⬇︎⠀DOC_MUTATORS
1265 * @const
1266 *
1267 * @type {string}
1268 */
1269 static get DOC_MUTATIONS() {
1270 return 'mutators';
1271 }
1272
1273 /**
1274 * A constant key used to identify a comment for the top level subscription
1275 * description
1276 *
1277 * @static
1278 * @memberof GQLBase
1279 * @method ⬇︎⠀DOC_SUBSCRIPTION
1280 * @const
1281 *
1282 * @type {string}
1283 */
1284 static get DOC_SUBSCRIPTION() {
1285 return 'subscription';
1286 }
1287
1288 /**
1289 * A constant key used to identify a comment for a subscription description
1290 *
1291 * @static
1292 * @memberof GQLBase
1293 * @method ⬇︎⠀DOC_SUBSCRIPTIONS
1294 * @const
1295 *
1296 * @type {string}
1297 */
1298 static get DOC_SUBSCRIPTIONS() {
1299 return 'subscriptions';
1300 }
1301
1302 /**
1303 * A shortcut to the utils/joinLines function to make it easier to get
1304 * the tools to write docs for your types in a friendly fashion.
1305 *
1306 * @memberof GQLBase
1307 * @method ⬇︎⠀joinLines
1308 * @static
1309 * @const
1310 *
1311 * @type {Function}
1312 */
1313 static get joinLines() {
1314 return _utils.joinLines;
1315 }
1316
1317 /**
1318 * An simple pass-thru method for fetching a types merged root object.
1319 *
1320 * @method ⌾⠀getMergedRoot
1321 * @memberof GQLBase
1322 * @static
1323 *
1324 * @param {Object} requestData an object containing the request data such as
1325 * request, response or graphql context info that should be passed along to
1326 * each of the resolver creators
1327 * @return {Object} the merged root object with all the query, mutation and
1328 * subscription resolvers defined and created within.
1329 */
1330 static getMergedRoot(requestData, separateByType = false) {
1331 var _this4 = this;
1332
1333 return (0, _asyncToGenerator3.default)(function* () {
1334 const root = {};
1335 const Class = _this4;
1336
1337 let _ = {
1338 // $FlowFixMe
1339 resolvers: Class[META_KEY].resolvers || [],
1340 // $FlowFixMe
1341 mutators: Class[META_KEY].mutators || [],
1342 // $FlowFixMe
1343 subscriptors: Class[META_KEY].subscriptors || []
1344 };
1345
1346 let convert = function (f) {
1347 let isFactoryClass = function (c) {
1348 return !!Class[META_KEY][(0, _for2.default)('Factory Class')];
1349 };
1350
1351 if (isFactoryClass(Class)) {
1352 return {
1353 [f.name]: function (...args) {
1354 return f.apply(Class, [Class, requestData, ...args]);
1355 }
1356 };
1357 } else {
1358 return {
1359 [f.name]: function (...args) {
1360 return f.apply(Class, [requestData, ...args]);
1361 }
1362 };
1363 }
1364 };
1365 let reduce = function (p, c) {
1366 return (0, _lodash.merge)(p, c);
1367 };
1368
1369 _.resolvers = _.resolvers.map(convert).reduce(reduce, {});
1370 _.mutators = _.mutators.map(convert).reduce(reduce, {});
1371 _.subscriptors = _.subscriptors.map(convert).reduce(reduce, {});
1372
1373 if (separateByType) {
1374 // Apollo wants all the resolvers to grouped by top level type.
1375 // The field resolvers aren't an issue in Lattice defined types
1376 // but the root types do need to be sorted; so let's do that here
1377 (0, _lodash.merge)(root, { Query: yield Class.RESOLVERS(requestData) }, { Mutation: yield Class.MUTATORS(requestData) }, { Query: _.resolvers }, { Mutation: _.mutators }, { Subscription: _.subscriptors });
1378
1379 // When using lattice with apollo server, it is quite particular about
1380 // empty Query, Mutation or Subscription resolver maps.
1381 if (!(0, _keys2.default)(root.Query).length) delete root.Query;
1382 if (!(0, _keys2.default)(root.Mutation).length) delete root.Mutation;
1383 if (!(0, _keys2.default)(root.Subscription).length) delete root.Subscription;
1384 } else {
1385 (0, _lodash.merge)(root, (yield Class.RESOLVERS(requestData)), (yield Class.MUTATORS(requestData)), _.resolvers, _.mutators, _.subscriptors);
1386 }
1387
1388 return root;
1389 })();
1390 }
1391
1392 /**
1393 * An object used to store data used by decorators and other internal
1394 * proccesses.
1395 * @ComputedType
1396 */
1397 static get [META_KEY]() {
1398 let storage = this[(0, _for2.default)(this.name)];
1399
1400 if (!storage) {
1401 storage = this[(0, _for2.default)(this.name)] = {};
1402 }
1403
1404 return storage;
1405 }
1406};
1407exports.default = GQLBase;
1408//# sourceMappingURL=GQLBase.js.map
\No newline at end of file