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