UNPKG

30.8 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.isFactoryClass = exports.LatticeFactory = exports.ValidationResults = exports.CHECK_API_DOCS = exports.CHECK_RESOLVERS = exports.CHECK_SCHEMA = exports.CHECKLIST = undefined;
7
8var _entries = require('babel-runtime/core-js/object/entries');
9
10var _entries2 = _interopRequireDefault(_entries);
11
12var _getOwnPropertyDescriptor = require('babel-runtime/core-js/object/get-own-property-descriptor');
13
14var _getOwnPropertyDescriptor2 = _interopRequireDefault(_getOwnPropertyDescriptor);
15
16var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
17
18var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
19
20var _defineProperty = require('babel-runtime/core-js/object/define-property');
21
22var _defineProperty2 = _interopRequireDefault(_defineProperty);
23
24var _keys = require('babel-runtime/core-js/object/keys');
25
26var _keys2 = _interopRequireDefault(_keys);
27
28var _toStringTag = require('babel-runtime/core-js/symbol/to-string-tag');
29
30var _toStringTag2 = _interopRequireDefault(_toStringTag);
31
32var _for = require('babel-runtime/core-js/symbol/for');
33
34var _for2 = _interopRequireDefault(_for);
35
36exports.getChecklist = getChecklist;
37exports.setChecklist = setChecklist;
38exports.hasChecklist = hasChecklist;
39exports.newChecklist = newChecklist;
40
41var _GQLBase = require('./GQLBase');
42
43var _GQLEnum = require('./GQLEnum');
44
45var _GQLInterface = require('./GQLInterface');
46
47var _GQLScalar = require('./GQLScalar');
48
49var _SyntaxTree = require('./SyntaxTree');
50
51var _neTagFns = require('ne-tag-fns');
52
53var _neTypes = require('ne-types');
54
55var _util = require('util');
56
57var _Resolvers = require('./decorators/Resolvers');
58
59var _ModelProperties = require('./decorators/ModelProperties');
60
61var _utils = require('./utils');
62
63function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
64
65const _i = (...args) => (0, _util.inspect)(...args, { colors: true, depth: 3 });
66
67/**
68 * The CHECKLIST Symbol is used as a storage key in the metadata staging
69 * area for each of the GQLBase extended classes. In the LatticeFactory
70 * it is used to determine where in the flow of construction the class
71 * currently is.
72 *
73 * @type {Symbol}
74 */
75
76// import { GQLUnion } from './GQLUnion'
77
78
79const CHECKLIST = exports.CHECKLIST = (0, _for2.default)('checklist');
80
81/**
82 * The CHECK_SCHEMA Symbol is part of the CHECKLIST for a constructed
83 * GQLBase extended class. It denotes that the class has had its SCHEMA
84 * getter defined.
85 *
86 * @type {Symbol}
87 */
88const CHECK_SCHEMA = exports.CHECK_SCHEMA = (0, _for2.default)('checklist-schema');
89
90/**
91 * The CHECK_RESOLVERS Symbol is part of the CHECKLIST for a constructed
92 * GQLBase extended class. It denotes that the class has had its instance
93 * field resolvers as well as its static Query, Mutation and Subscription
94 * resolvers injected and defined.
95 *
96 * @type {Symbol}
97 */
98const CHECK_RESOLVERS = exports.CHECK_RESOLVERS = (0, _for2.default)('checklist-resolvers');
99
100/**
101 * The CHECK_API_DOCS Symbol is part of the CHECKLIST for a constructed
102 * GQLBase extended class. It denotes that the class has had its api docs
103 * defined, processed and setup on the class in a way that it will be
104 * picked up in the build lifecycle.
105 *
106 * @type {Symbol}
107 */
108const CHECK_API_DOCS = exports.CHECK_API_DOCS = (0, _for2.default)('checklist-api-docs');
109
110/**
111 * Peeks into the metadata storage area of a given GQLBase extended
112 * class and fetches the factory checklist if one exists.
113 *
114 * @param {Function} Class a reference to the GQLBase class to peek in
115 * @return {Object} an object setup with at least three booleans keyed by
116 * the constants CHECK_SCHEMA, CHECK_RESOLVERS, and CHECK_API_DOCS or null
117 * if none exists
118 */
119function getChecklist(Class) {
120 return Class && Class[_GQLBase.META_KEY] && Class[_GQLBase.META_KEY][CHECKLIST] || null;
121}
122
123/**
124 * Obtains the checklist from the supplied GQLBase extended class. If the
125 * class has a checklist, its checklist item is set to true or the boolean
126 * value specified.
127 *
128 * @param {Function} Class a reference to the GQLBase class to set
129 * @param {Symbol} item one of CHECK_SCHEMA, CHECK_RESOLVERS, or
130 * CHECK_API_DOCS
131 * @param {Boolean} value the value for the checklist item to set
132 */
133function setChecklist(Class, item, value = true) {
134 let checklist = getChecklist(Class);
135
136 if (checklist) {
137 // $FlowFixMe
138 checklist[item] = value;
139 }
140}
141
142/**
143 * This function, when invoked with only a class will return true if the
144 * Class has a defined checklist. If one ore more CHECKLIST symbols are
145 * passed, the function will only return true if all the supplied symbols
146 * are set to truthy values.
147 *
148 * @param {Function} Class a reference to the GQLBase class to set
149 * @param {Array<Symbol>} items any of CHECK_SCHEMA, CHECK_RESOLVERS, or
150 * CHECK_API_DOCS
151 * @return {Boolean} true if the checklist and/or all items are true and
152 * present.
153 */
154function hasChecklist(Class, ...items) {
155 let checklist = getChecklist(Class);
156
157 if (checklist && items.length) {
158 for (let item of items) {
159 if (!checklist[item]) {
160 return false;
161 }
162 }
163
164 return true;
165 }
166
167 return checklist;
168}
169
170/**
171 * Injects and creates a new CHECKLIST object on the supplied GQLBase
172 * extended class. All items are installed and set to false.
173 *
174 * @param {Function} Class a reference to the GQLBase class to set
175 */
176function newChecklist(Class) {
177 if (Class) {
178 // $FlowFixMe
179 Class[_GQLBase.META_KEY][CHECKLIST] = {
180 [CHECK_SCHEMA]: false,
181 [CHECK_RESOLVERS]: false,
182 [CHECK_API_DOCS]: false,
183
184 get keys() {
185 return [CHECK_SCHEMA, CHECK_RESOLVERS, CHECK_API_DOCS];
186 },
187
188 get complete() {
189 return this.keys.reduce((p, c, i, a) => {
190 if (!p || !this[c]) {
191 return false;
192 }
193 }, true);
194 }
195 };
196 } else {
197 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
198 Cannot create new checklist metadata on a non-existent class
199 `);
200 }
201}
202
203let ValidationResults = exports.ValidationResults = class ValidationResults {
204
205 constructor(errors = []) {
206 this.errors = errors;
207 }
208
209 get valid() {
210 return this.errors.length === 0;
211 }
212
213 // $FlowFixMe
214 get [_toStringTag2.default]() {
215 return this.constructor.name;
216 }
217
218 // $FlowFixMe
219 static get [_toStringTag2.default]() {
220 return this.name;
221 }
222};
223let LatticeFactory = exports.LatticeFactory = class LatticeFactory {
224
225 /**
226 * Walks through a supplied template object and collects errors with its
227 * format before bubbling up an exception if any part of it fails to
228 * pass muster. The exception can be prevented from throwing if hide is set
229 * to true
230 *
231 * @param {Object} template an object to be parsed for construction via the
232 * Lattice Factory
233 * @param {boolean} hide if true, an invalid template will NOT throw errors
234 * @return {ValidationResults} a `ValidationResults` object containing the
235 * collected errors and a `valid` that is dynamically calculated.
236 */
237 static validateTemplate(template, hide = false) {
238 let results = new ValidationResults();
239 let indent = (string, count = 4, space = ' ') => string.split('\n').map(s => s.trim().replace(/(^)/gm, `$1${space.repeat(count)}`)).join('\n');
240
241 if (typeof template.name === 'undefined') {
242 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
243 The \`template.name\` field must exist or the creation for the Lattice
244 factory class generation to succeed.
245
246 Please read the documentation for more information on the format of
247 a LatticeFactory template.
248 `));
249 }
250
251 if (!(0, _neTypes.extendsFrom)(template.name, String)) {
252 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
253 The \`template.name\` field must be a string.
254
255 Please read the documentation for more information on the format of
256 a LatticeFactory template.
257 `));
258 }
259
260 if (typeof template.schema === 'undefined') {
261 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
262 The \`template.schema\` field must exist or the creation for the
263 Lattice factory class generation to succeed.
264
265 Please read the documentation for more information on the format of
266 a LatticeFactory template.
267 `));
268 }
269
270 if (!(0, _neTypes.extendsFrom)(template.schema, String)) {
271 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
272 The \`template.schema\` field must be a string of GraphQL SDL/IDL
273
274 Please read the documentation for more information on the format of
275 a LatticeFactory template.
276 `));
277 }
278
279 if (!(0, _neTypes.extendsFrom)(template.resolvers, Object) // Supports 95% of objects
280 || typeof template.resolvers !== 'object' // Supports Object.create(null)
281 ) {
282 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`\x1b[91;1m
283 The \`template.resolvers\` field must be an Object containing the
284 resolver functions. Query, Mutation and Subscription resolvers will
285 take the following signature. Additionally, the keys for these special
286 resolvers must be Query, Mutation or Subscription; respectively
287 \x1b[37;22m
288 Query: { [resolver]: (requestData, resolverParameters) => {} }
289 Mutation: { [resolver]: (requestData, resolverParameters) => {} }
290 Subscription: { [resolver]: (requestData, resolverParameters) => {} }
291
292 where:
293 \`requestData\` is an object with { req, res, gql|next } depending
294 on the graphql server implementation (FB Reference, Apollo, etc)
295 \`resovlerParameters\` is an object with keys matching those
296 parameters defined in the SCHEMA for the resolver in question.
297 \x1b[91;1m
298 Field resolvers should be found under the key name of the type
299 or interface in question and must correspond to the following signature
300 \x1b[37;22m
301 [Type]: { [resolver]: (resolverParameters) => {} }
302
303 where:
304 \`Type\` is the name of the GQL type defined in the schema
305 \`resovlerParameters\` is an object with keys matching those
306 parameters defined in the SCHEMA for the resolver in question.
307
308 * it is worth noting that the field resolvers are not static and
309 can access the \`requestData\` object via \`this.requestData\`
310 \x1b[91;1m
311 Please read the documentation for more information on the format of
312 a LatticeFactory template.\x1b[0m
313 `));
314 }
315
316 if (typeof template.docs === 'undefined') {
317 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
318 The \`template.docs\` field must exist for the creation of the
319 Lattice factory class generation to succeed.
320
321 Please read the documentation for more information on the format of
322 a LatticeFactory template.
323 `));
324 }
325
326 if (!(0, _neTypes.extendsFrom)(template.docs, Object) // Supports 95% of objects
327 || typeof template.docs !== 'object' // Supports Object.create(null)
328 ) {
329 let dr = '\x1b[31m',
330 br = '\x1b[91m';
331 let b1 = '\x1b[1m',
332 b0 = '\x1b[22m';
333 let bb = '\x1b[90m';
334 let dg = '\x1b[37m',
335 bg = '\x1b[97m';
336 let a0 = '\x1b[0m';
337 let gr = '\x1b[32m',
338 bgr = '\x1b[92m';
339
340 results.errors.push(new Error((0, _neTagFns.customDedent)({ dropLowest: true })`\x1b[1;91m
341 The \`template.docs\` field must be an object containing keys and
342 value pairs matching the types, enums, unions and interfaces defined
343 in your schema.
344
345 The special Symbol object TYPE can be used to reference the docs for
346 the named or keyed field describing the documentation to be processed
347 Comments for the \`Query\`, \`Mutation\`, and \`Subscription\` [TYPE]
348 entries will replace any previous one that comes before it. Typically
349 this field is best left undescribed since there will ever only be
350 one of each at most.
351
352 \x1b[22;31mExamples should look something like this:\x1b[22;37m
353 import { TYPE, joinLines } from 'graphql-lattice'
354
355 export default {
356 ${bb}/* other fields */${dg}
357
358 ${b1}schema:${b0} joinLines${gr}\`
359 type Person { id: ID name: String }
360 type Query { findPerson(id: ID): Person }
361 type Mutation { setPersonName(id: ID, name: String): Person }
362 \`${dg},
363
364 ${b1}docs:${b0} {
365 ${b1}Person:${b0} {
366 [TYPE]: ${gr}'A contrived person type'${dg},
367 id: ${gr}'A unique identifier for a person'${dg},
368 name: ${gr}'A string denoting the name of a person'${dg}
369 },
370 ${b1}Query:${b0} {
371 findPerson: ${gr}'A query taking an ID, returns a Person'${dg},
372 },
373 ${b1}Mutation:${b0} {
374 setPersonName: joinLines${gr}\`
375 A mutation that sets the name of the user identified by an
376 ID to the new name value supplied
377 \`${dg}
378 }
379 }
380 }
381 \x1b[22;31m
382 Note the usage of \`Person\`, \`Query\` and \`Mutation\` explicitly
383 as keys to the supplied \`docs\` object.\x1b[0m
384 `));
385 }
386
387 if (!results.valid) {
388 let errorStrings = [];
389
390 for (let error of results.errors) {
391 let { message, stack } = error;
392
393 stack = stack.trim().split('\n').splice(message.split('\n').length).map(s => s.trim()).join('\n');
394 message = message.replace(/(Error:\s)/, '$1\n').trim();
395
396 errorStrings.push(`\x1b[31;1m${message}\x1b[0m\n` + indent(stack));
397 }
398
399 let error = new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
400 OOPS!
401
402 An error occurred validating your factory template. The object
403 in question is as follows:
404
405 @template
406
407 The individual errors that occurred are:
408 \n@errors`.replace(/@template/, indent(_i(template))).replace(/@errors/, errorStrings.join('\n\n')));
409
410 error.stack = error.message;
411 error.message = '';
412
413 if (!hide) throw error;
414 }
415
416 return results;
417 }
418
419 /**
420 * The starting point of a LatticeFactory object -> class creation. The name
421 * of the class and baseClass to use are provided and are created from there.
422 * At this point, the generated class is still incomplete. It must complete
423 * the entire checklist before being deemed valid.
424 *
425 * @param {string} name name of the class to create
426 * @param {Function} baseClass the Lattice class your new class should extend;
427 * while this can be anything, it should be GQLBase, GQLInterface, GQLEnum or
428 * GQLUnion. This defaults to GQLBase should nothing be supplied
429 * @return {Function} actually this returns the generated class
430 */
431 static generateClass(name, baseClass = _GQLBase.GQLBase) {
432 if (!name) {
433 throw new Error('LatticeFactory.generateClass needs a name!!');
434 }
435
436 // Alright ladies and gentlemen, hold onto your hats; we're entering the
437 // meta zone!!! The way the following works is to make sure that our
438 // passed in base class `baseClass` is actually in scope as the name of
439 // the value it represents. We use the `new Function()` syntax to do that
440 // but we do it via eval since we don't know the name of the function
441 // at the time we write the code
442 //
443 // So given a class name of "Car" and baseName equalling GQLBase, the Class
444 // instance, fn would look something like the results of calling this
445 //
446 // let fn = new Function(
447 // "GQLBase",
448 // "class Car extends GQLBase {}; return Car;"
449 // )
450 //
451 // Which in turn sets fn to something that would be the same as
452 //
453 // function fn(GQLBase) { class Car extends GQLBase {}; return Car }
454 //
455 // Which means that when we invoke fn(baseClass), which is fn(GQLBase),
456 // we get the results we intend; even if GQLBase is not necessarily in
457 // the scope of the function at the time of call. Neat. Scary. OMG Thanks
458 // for code comments. You're welcome future me.
459 let fn = eval(`(new Function(
460 "${baseClass.name}",
461 "class ${name} extends ${baseClass.name} {}; return ${name};"
462 ))`);
463
464 let Class = fn(baseClass);
465
466 this.brandClass(Class);
467 newChecklist(Class);
468
469 return Class;
470 }
471
472 /**
473 * Injects the SCHEMA property into the newly defined class. The supplied
474 * `schema` string becomes what the new class returns when `.SCHEMA` is
475 * gotten.
476 *
477 * @param {Function} Class this will throw an error if the class is not one
478 * generated by the LatticeFactory or if the class itself is null or undefined
479 * @param {string} schema the string that the new class should return
480 * @return {Function} returns the modified Class with the `CHECK_SCHEMA`
481 * portion ticked off internally.
482 */
483 static injectSchema(Class, schema) {
484 if (!Class || !hasChecklist(Class)) {
485 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
486 Either the supplied schema string is invalid
487 SCHEMA: \`
488 ${schema}
489 \`
490
491 Or your supplied class ${Class && Class.name || 'undefined'} is
492 non-existent. Please check your code and try the LatticeFactory
493 again.
494 `);
495 }
496
497 // $FlowFixMe
498 Object.defineProperty(Class, 'SCHEMA', {
499 get() {
500 return schema;
501 }
502 });
503
504 setChecklist(Class, CHECK_SCHEMA);
505
506 return Class;
507 }
508
509 /**
510 * Injects the resolvers into appropriate areas. Resolvers keyed by `Query`,
511 * `Mutation`, or `Subscription` will be placed into the appropriate area
512 * in `Class[META_KEY]` which acts as a staging area originally designed for
513 * use with the @resolver, @mutator and @subscriptor decorators. These will
514 * be bound in a typical fashion as is done with the decorators making the
515 * first parameter becoming the requestData of the object instance and the
516 * second being the object containing the parameters for the resolver as
517 * passed in by GraphQL. Subsequent parameters will be supplied as is the
518 * fashion of the system you're using; Facebook's reference implementation or
519 * Apollo or something else.
520 *
521 * Resolvers keyed by type name are considered to be field resolvers and
522 * have a different signature. They can be properties of the key, in
523 * which case they will simply be installed as getters. Or they can be
524 * functions; synchronous or asynchronous. Function field resolvers are
525 * instance methods and can make use of `this.getModel()` or
526 * `this.requestData` internally.
527 *
528 * @param {Function} Class the class, generated by generateClass() lest an
529 * error be thrown, to which to add the resolvers from a template
530 * @param {Object} resolverObj an object containing the resolvers as dictated
531 * by the new format.
532 * @return {Function} returns the modified Class with the `CHECK_RESOLVERS`
533 * portion ticked off internally.
534 */
535 static injectResolvers(Class, resolvers) {
536 if (!hasChecklist(Class, CHECK_SCHEMA)) {
537 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
538 \`injectResolvers\` cannot be called on a class without a SCHEMA.
539 Please verify your progress in the process and try again.
540 `);
541 }
542
543 let tree = _SyntaxTree.SyntaxTree.from(Class.SCHEMA);
544 let outline = tree ? tree.outline : {};
545
546 if (Class.name in outline && Class.name in resolvers) {
547 let fields = (0, _keys2.default)(outline[Class.name]);
548
549 for (let fieldResolver of fields) {
550 if (!fieldResolver in resolvers[Class.name]) {
551 _utils.LatticeLogs.warn((0, _neTagFns.customDedent)({ dropLowest: true })`
552 ${fieldResolver} not supplied in resolvers for ${Class.name}
553 `);
554 continue;
555 }
556
557 let prop = resolvers[Class.name][fieldResolver];
558
559 if (prop && typeof prop === 'function') {
560 _utils.LatticeLogs.info('Injecting [fn] %s', fieldResolver);
561 (0, _defineProperty2.default)(Class.prototype, fieldResolver, {
562 value: prop
563 });
564 } else {
565 _utils.LatticeLogs.info('Injecting [prop] %s', fieldResolver);
566 (0, _ModelProperties.Properties)(fieldResolver)(Class, ['factory-props']);
567 }
568 }
569 }
570
571 for (let [type, decorator] of [['Query', _Resolvers.resolver], ['Mutation', _Resolvers.mutator], ['Subscription', _Resolvers.subscriptor]]) {
572 let keys = (0, _keys2.default)(outline[type] || {});
573
574 // $FlowFixMe
575 if (!type in outline || !keys.length) {
576 continue;
577 }
578
579 for (let fnName of keys) {
580 let fn = resolvers[fnName];
581 decorator(Class, fnName, { value: fn });
582 _utils.LatticeLogs.info('Adding %s resolver [%s]', type, fnName);
583 }
584 }
585
586 setChecklist(Class, CHECK_RESOLVERS);
587
588 return Class;
589 }
590
591 static injectDocs(Class, docs) {
592 if (!hasChecklist(Class, CHECK_SCHEMA, CHECK_RESOLVERS)) {
593 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
594 \`injectDocs\` cannot be called on a class without a SCHEMA or
595 RESOLVERS defined. Please verify your progress in the process and try
596 again.
597 `);
598 }
599
600 let copyProp = (o, prop, to, as) => {
601 // $FlowFixMe
602 let prototype = o.prototype || (0, _getPrototypeOf2.default)(o);
603 let descriptor = (0, _getOwnPropertyDescriptor2.default)(prototype, prop);
604
605 if (!as) {
606 as = prop;
607 }
608
609 if (descriptor) {
610 (0, _defineProperty2.default)(to, as, descriptor);
611 } else {
612 // $FlowFixMe
613 to[as] = o[prop];
614 }
615 };
616
617 // Create an object our future `static apiDocs()` method of our factory
618 // generated class will return
619 let result = {};
620
621 // Setup the constants we will need in this conversion
622 const { TYPE } = this;
623 const {
624 DOC_CLASS, DOC_FIELDS, DOC_QUERY, DOC_MUTATION, DOC_SUBSCRIPTION,
625 DOC_QUERIES, DOC_MUTATIONS, DOC_SUBSCRIPTIONS
626 } = _GQLBase.GQLBase;
627
628 // This part might get a little meta, so I have provided comments. You are
629 // welcome future me. I hope it helps. This gnarly block should cover all
630 // the descriptions for Query, Mutation, Subscription and the Class we
631 // are creating. Other superfluous
632 for (let [Type, TopLevelConstant, FieldConstants] of [['Query', DOC_QUERY, DOC_QUERIES], ['Mutation', DOC_MUTATION, DOC_MUTATIONS], ['Subscription', DOC_SUBSCRIPTION, DOC_SUBSCRIPTIONS], [Class.name, DOC_CLASS, DOC_FIELDS]]) {
633 // One of 'Query', 'Mutation', or 'Subscription'
634 if (docs[Type]) {
635 // If a top level description is present (i.e. Query, Mutation or
636 // Subscription description)
637 if (docs[Type][TYPE]) {
638 copyProp(docs[Type], TYPE, result, TopLevelConstant);
639 }
640
641 // Fetch the properties from the supplied docs object; TYPE Symbols
642 // do not show up in a call to entries which is why it is handled above
643 // $FlowFixMe
644 let entries = (0, _entries2.default)(docs[Type]);
645
646 // If we have entries to document, create an object to hold those
647 // values; i.e. if we have `{ Query: { getPeople: 'desc' } }`, we need
648 // to make sure we have `{ [DOC_QUERIES]: { getPeople: 'desc' } }` in
649 // our result. The object holding getPeople in the end there is defined
650 // below when we have something to copy.
651 if (entries.length) {
652 result[FieldConstants] = {};
653 }
654
655 // For each name value pair defined above, copy its descriptor or base
656 // value if a descriptor isn't available
657 for (let [prop, value] of entries) {
658 copyProp(docs[Type], prop, result[FieldConstants]);
659 }
660 }
661 }
662
663 Object.defineProperty(Class, 'apiDocs', {
664 value: function () {
665 return result;
666 }
667 });
668
669 setChecklist(Class, CHECK_API_DOCS);
670
671 return Class;
672 }
673
674 static build(template) {
675 let validationResults = this.validateTemplate(template);
676 let Class = this.generateClass(template.name, template.type || _GQLBase.GQLBase);
677
678 if (!Class) {
679 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
680 LatticeFactory was unable to build your Class from the name and types
681 supplied in your template. You provided the following template. Please
682 look it over and correct any errors before trying again.
683
684 \x1b[1mTemplate\x1b[0m
685 ${_i(template)}
686 `);
687 }
688
689 this.injectSchema(Class, template.schema);
690 this.injectResolvers(Class, template.resolvers || {});
691 this.injectDocs(Class, template.docs || {});
692
693 // Need to fix how auto-props work; for now create one instance...
694 new Class({});
695
696 if (!hasChecklist(Class, CHECK_SCHEMA, CHECK_RESOLVERS, CHECK_API_DOCS)) {
697 let _schema = hasChecklist(Class, CHECK_SCHEMA) ? '✅' : '❌';
698 let _resolvers = hasChecklist(Class, CHECK_RESOLVERS) ? '✅' : '❌';
699 let _apiDocs = hasChecklist(Class, CHECK_API_DOCS) ? '✅' : '❌';
700
701 throw new Error((0, _neTagFns.customDedent)({ dropLowest: true })`
702 Something went wrong in the process of building the class called
703 ${Class && Class.name || template && template.name || 'Unknown!'},
704 please check the supplied template for errors.
705
706 [ ${_schema} ] Has a SCHEMA defined
707 [ ${_resolvers} ] Has defined RESOLVERS matching the SCHEMA
708 [ ${_apiDocs} ] Has defined API Docs matching the SCHEMA
709
710 \x1b[1mTemplate\x1b[0m
711 ${_i(template)}
712
713 \x1b[1mClass\x1b[0m
714 ${_i(Class)}
715 `);
716 }
717
718 return Class;
719 }
720
721 /**
722 * A static helper method to consistently tag, or brand, classes with a
723 * symbol that denotes they were created using the LatticeFactory process.
724 * This is done by setting a `Symbol` on the root of the class or in the
725 * `[META_KEY]` object for classes extending `GQLBase`.
726 *
727 * @method ⌾⠀brandClass
728 * @memberof LatticeFactory
729 * @static
730 *
731 * @param {Function} Class the class to brand with the `FACTORY_CLASS` symbol
732 * @return {Function} returns the Class value passed in
733 */
734 static brandClass(Class) {
735 if (Class) {
736 if ((0, _neTypes.extendsFrom)(Class, _GQLBase.GQLBase)) {
737 Class[_GQLBase.META_KEY][this.FACTORY_CLASS] = true;
738 } else {
739 Class[this.FACTORY_CLASS] = true;
740 }
741 }
742
743 return Class;
744 }
745
746 /**
747 * A static helper to check and see if the supplied class or function was
748 * branded with the `brandClass()` function. This amounts to storing the
749 * boolean true under the property `Class[LatticeFactory.FACTORY_CLASS]` or
750 * `Class[META_KEY][LatticeFacatory.FACTORY_CLASS]` for `GQLBase` extended
751 * classes.
752 *
753 * @method ⌾⠀isFactoryClass
754 * @memberof LatticeFactory
755 * @static
756 *
757 * @param {Function} Class the class to check for `FACTORY_CLASS` branding
758 * @return {boolean} true if the brand exists, false otherwise
759 */
760 static isFactoryClass(Class) {
761 if (Class) {
762 return (0, _neTypes.extendsFrom)(Class, _GQLBase.GQLBase) ? !!Class[_GQLBase.META_KEY][this.FACTORY_CLASS] : !!Class[this.FACTORY_CLASS];
763 }
764
765 return false;
766 }
767
768 /**
769 * A static helper method to consistently remove any previous tag or brand
770 * applied with `brandClass`, this is done by removing a previously set
771 * `Symbol` on the root of the class or in the `[META_KEY]` object for
772 * classes extending `GQLBase`.
773 *
774 * @method ⌾⠀removeClassBrand
775 * @memberof LatticeFactory
776 * @static
777 *
778 * @param {Function} Class the class to brand with the `FACTORY_CLASS` symbol
779 * @return {Function} returns the Class value passed in
780 */
781 static removeClassBrand(Class) {
782 if (Class) {
783 if ((0, _neTypes.extendsFrom)(Class, _GQLBase.GQLBase)) {
784 delete Class[_GQLBase.META_KEY][this.FACTORY_CLASS];
785 } else {
786 delete Class[this.FACTORY_CLASS];
787 }
788 }
789
790 return Class;
791 }
792
793 /**
794 * A constant that reports that this class is `'[object LatticeFactory]'`
795 * rather than `'[object Object]'` when introspected with tools such as
796 * `Object.prototype.toString.apply(class)`.
797 *
798 * @memberof LatticeFactory
799 * @type {Symbol}
800 * @static
801 */
802 // $FlowFixMe
803 static get [_toStringTag2.default]() {
804 return this.name;
805 }
806
807 /**
808 * A constant exported as part of LatticeFactory that can be used for
809 * defining documentation for the type itself.
810 *
811 * @memberof LatticeFactory
812 * @type {Symbol}
813 * @static
814 */
815 static get TYPE() {
816 return (0, _for2.default)('API Docs Type Constant');
817 }
818
819 /**
820 * A constant exported as part of LatticeFactory that can be used for
821 * identifying classes that were generated with LatticeFactory.
822 *
823 * @memberof LatticeFactory
824 * @type {Symbol}
825 * @static
826 */
827 static get FACTORY_CLASS() {
828 return (0, _for2.default)('Factory Class');
829 }
830};
831const isFactoryClass = exports.isFactoryClass = LatticeFactory.isFactoryClass;
832
833// TESTING REPL
834/**
835var { LatticeFactory, getChecklist, hasChecklist, CHECKLIST, CHECK_SCHEMA, CHECK_RESOLVERS } = require('./dist/LatticeFactory'); var { GQLBase, META_KEY, joinLines, SyntaxTree, typeOf } = require('./dist/lattice'); var gql = joinLines, LF = LatticeFactory, TYPE = LF.TYPE;
836var PersonDef = { name: 'Person', schema: gql` enum StatType { PHYSICAL, MENTAL } type Person { name: String stats(type:StatType): Stat } type Query { findPerson(id: ID): Person } `, resolvers: { Query: { findPerson({req, res, next}, {id}) { console.log('find person') } }, Person: { stats({type}) { let { req, res, next} = this.requestData } } }, docs: { StatType: { [TYPE]: `A type of statistic associated with people`, PHYSICAL: `Physical attributes`, MENTAL: `Mental attributes` }, Person: { [TYPE]: `Represents a person`, personId: `Unique id of the person in question`, name: `The name of the person`, stats: `Allows you to query the stats of a person based on type` }, Query: { [TYPE]: 'Top level query desc.', findPerson: `Searches the system for the specified user` } } };
837var Person = LF.build(PersonDef), p = new Person({name: 'Brielle'})
838Person.getProp('stats',true,{requestData:{req:1,res:2,next:3}})
839var Broke = LF.build({name: 'Broke', schema: gql`type Broke {name: String}`, resolvers:{}, docs:{}})
840var t = LF.validateTemplate({name: '', type: GQLBase, resolvers: {}, docs: {}, schema: ''});
841*/
842//# sourceMappingURL=LatticeFactory.js.map
\No newline at end of file