1 |
|
2 |
|
3 | import { GQLBase, META_KEY } from './GQLBase'
|
4 | import { GQLEnum } from './GQLEnum'
|
5 | import { GQLInterface } from './GQLInterface'
|
6 | import { GQLScalar } from './GQLScalar'
|
7 |
|
8 | import { SyntaxTree } from './SyntaxTree'
|
9 | import { customDedent } from 'ne-tag-fns'
|
10 | import { typeOf, extendsFrom } from 'ne-types'
|
11 | import { inspect } from 'util'
|
12 | import { resolver, mutator, subscriptor } from './decorators/Resolvers'
|
13 | import { Properties } from './decorators/ModelProperties'
|
14 | import { LatticeLogs as LL } from './utils'
|
15 |
|
16 | const _i = (...args) => inspect(...args, {colors: true, depth: 3})
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | export const CHECKLIST = Symbol.for('checklist')
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 | export const CHECK_SCHEMA = Symbol.for('checklist-schema')
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | export const CHECK_RESOLVERS = Symbol.for('checklist-resolvers')
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | export const CHECK_API_DOCS = Symbol.for('checklist-api-docs')
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 |
|
67 | export function getChecklist(Class: Function) {
|
68 | return (Class && Class[META_KEY] && Class[META_KEY][CHECKLIST]) || null
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | export function setChecklist(
|
82 | Class: Function,
|
83 | item: Symbol,
|
84 | value: boolean = true
|
85 | ) {
|
86 | let checklist = getChecklist(Class)
|
87 |
|
88 | if (checklist) {
|
89 |
|
90 | checklist[item] = value
|
91 | }
|
92 | }
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 | export function hasChecklist(Class: Function, ...items: Array<Symbol>) {
|
107 | let checklist = getChecklist(Class)
|
108 |
|
109 | if (checklist && items.length) {
|
110 | for (let item of items) {
|
111 | if (!checklist[item]) {
|
112 | return false
|
113 | }
|
114 | }
|
115 |
|
116 | return true
|
117 | }
|
118 |
|
119 | return checklist
|
120 | }
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | export function newChecklist(Class: Function) {
|
129 | if (Class) {
|
130 |
|
131 | Class[META_KEY][CHECKLIST] = {
|
132 | [CHECK_SCHEMA]: false,
|
133 | [CHECK_RESOLVERS]: false,
|
134 | [CHECK_API_DOCS]: false,
|
135 |
|
136 | get keys() {
|
137 | return [
|
138 | CHECK_SCHEMA, CHECK_RESOLVERS, CHECK_API_DOCS
|
139 | ]
|
140 | },
|
141 |
|
142 | get complete() {
|
143 | return this.keys.reduce((p,c,i,a) => {
|
144 | if (!p || !this[c]) { return false }
|
145 | }, true)
|
146 | }
|
147 | }
|
148 | }
|
149 | else {
|
150 | throw new Error(customDedent({dropLowest:true})`
|
151 | Cannot create new checklist metadata on a non-existent class
|
152 | `)
|
153 | }
|
154 | }
|
155 |
|
156 | export class ValidationResults {
|
157 | errors: Array<Error>
|
158 |
|
159 | constructor(errors: Array<Error> = []) {
|
160 | this.errors = errors
|
161 | }
|
162 |
|
163 | get valid(): boolean { return this.errors.length === 0 }
|
164 |
|
165 |
|
166 | get [Symbol.toStringTag](): string { return this.constructor.name }
|
167 |
|
168 |
|
169 | static get [Symbol.toStringTag](): string { return this.name }
|
170 | }
|
171 |
|
172 | export class LatticeFactory {
|
173 |
|
174 | |
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | static validateTemplate(
|
187 | template: Object,
|
188 | hide: boolean = false
|
189 | ): ValidationResults {
|
190 | let results = new ValidationResults()
|
191 | let indent = (string: string, count: number = 4, space: string = ' ') => (
|
192 | string
|
193 | .split('\n')
|
194 | .map(s => s.trim().replace(/(^)/gm, `$1${space.repeat(count)}`))
|
195 | .join('\n')
|
196 | )
|
197 |
|
198 | if (typeof template.name === 'undefined') {
|
199 | results.errors.push(new Error(customDedent({dropLowest:true})`
|
200 | The \`template.name\` field must exist or the creation for the Lattice
|
201 | factory class generation to succeed.
|
202 |
|
203 | Please read the documentation for more information on the format of
|
204 | a LatticeFactory template.
|
205 | `))
|
206 | }
|
207 |
|
208 | if (!extendsFrom(template.name, String)) {
|
209 | results.errors.push(new Error(customDedent({dropLowest:true})`
|
210 | The \`template.name\` field must be a string.
|
211 |
|
212 | Please read the documentation for more information on the format of
|
213 | a LatticeFactory template.
|
214 | `))
|
215 | }
|
216 |
|
217 | if (typeof template.schema === 'undefined') {
|
218 | results.errors.push(new Error(customDedent({dropLowest:true})`
|
219 | The \`template.schema\` field must exist or the creation for the
|
220 | Lattice factory class generation to succeed.
|
221 |
|
222 | Please read the documentation for more information on the format of
|
223 | a LatticeFactory template.
|
224 | `))
|
225 | }
|
226 |
|
227 | if (!extendsFrom(template.schema, String)) {
|
228 | results.errors.push(new Error(customDedent({dropLowest:true})`
|
229 | The \`template.schema\` field must be a string of GraphQL SDL/IDL
|
230 |
|
231 | Please read the documentation for more information on the format of
|
232 | a LatticeFactory template.
|
233 | `))
|
234 | }
|
235 |
|
236 | if (
|
237 | !extendsFrom(template.resolvers, Object)
|
238 | || typeof template.resolvers !== 'object'
|
239 | ) {
|
240 | results.errors.push(new Error(customDedent({dropLowest:true})`\x1b[91;1m
|
241 | The \`template.resolvers\` field must be an Object containing the
|
242 | resolver functions. Query, Mutation and Subscription resolvers will
|
243 | take the following signature. Additionally, the keys for these special
|
244 | resolvers must be Query, Mutation or Subscription; respectively
|
245 | \x1b[37;22m
|
246 | Query: { [resolver]: (requestData, resolverParameters) => {} }
|
247 | Mutation: { [resolver]: (requestData, resolverParameters) => {} }
|
248 | Subscription: { [resolver]: (requestData, resolverParameters) => {} }
|
249 |
|
250 | where:
|
251 | \`requestData\` is an object with { req, res, gql|next } depending
|
252 | on the graphql server implementation (FB Reference, Apollo, etc)
|
253 | \`resovlerParameters\` is an object with keys matching those
|
254 | parameters defined in the SCHEMA for the resolver in question.
|
255 | \x1b[91;1m
|
256 | Field resolvers should be found under the key name of the type
|
257 | or interface in question and must correspond to the following signature
|
258 | \x1b[37;22m
|
259 | [Type]: { [resolver]: (resolverParameters) => {} }
|
260 |
|
261 | where:
|
262 | \`Type\` is the name of the GQL type defined in the schema
|
263 | \`resovlerParameters\` is an object with keys matching those
|
264 | parameters defined in the SCHEMA for the resolver in question.
|
265 |
|
266 | * it is worth noting that the field resolvers are not static and
|
267 | can access the \`requestData\` object via \`this.requestData\`
|
268 | \x1b[91;1m
|
269 | Please read the documentation for more information on the format of
|
270 | a LatticeFactory template.\x1b[0m
|
271 | `))
|
272 | }
|
273 |
|
274 | if (typeof template.docs === 'undefined') {
|
275 | results.errors.push(new Error(customDedent({dropLowest:true})`
|
276 | The \`template.docs\` field must exist for the creation of the
|
277 | Lattice factory class generation to succeed.
|
278 |
|
279 | Please read the documentation for more information on the format of
|
280 | a LatticeFactory template.
|
281 | `))
|
282 | }
|
283 |
|
284 | if (
|
285 | !extendsFrom(template.docs, Object)
|
286 | || typeof template.docs !== 'object'
|
287 | ) {
|
288 | let dr = '\x1b[31m', br = '\x1b[91m'
|
289 | let b1 = '\x1b[1m', b0 = '\x1b[22m'
|
290 | let bb = '\x1b[90m'
|
291 | let dg = '\x1b[37m', bg = '\x1b[97m'
|
292 | let a0 = '\x1b[0m'
|
293 | let gr = '\x1b[32m', bgr = '\x1b[92m'
|
294 |
|
295 | results.errors.push(new Error(customDedent({dropLowest:true})`\x1b[1;91m
|
296 | The \`template.docs\` field must be an object containing keys and
|
297 | value pairs matching the types, enums, unions and interfaces defined
|
298 | in your schema.
|
299 |
|
300 | The special Symbol object TYPE can be used to reference the docs for
|
301 | the named or keyed field describing the documentation to be processed
|
302 | Comments for the \`Query\`, \`Mutation\`, and \`Subscription\` [TYPE]
|
303 | entries will replace any previous one that comes before it. Typically
|
304 | this field is best left undescribed since there will ever only be
|
305 | one of each at most.
|
306 |
|
307 | \x1b[22;31mExamples should look something like this:\x1b[22;37m
|
308 | import { TYPE, joinLines } from 'graphql-lattice'
|
309 |
|
310 | export default {
|
311 | ${bb}/* other fields */${dg}
|
312 |
|
313 | ${b1}schema:${b0} joinLines${gr}\`
|
314 | type Person { id: ID name: String }
|
315 | type Query { findPerson(id: ID): Person }
|
316 | type Mutation { setPersonName(id: ID, name: String): Person }
|
317 | \`${dg},
|
318 |
|
319 | ${b1}docs:${b0} {
|
320 | ${b1}Person:${b0} {
|
321 | [TYPE]: ${gr}'A contrived person type'${dg},
|
322 | id: ${gr}'A unique identifier for a person'${dg},
|
323 | name: ${gr}'A string denoting the name of a person'${dg}
|
324 | },
|
325 | ${b1}Query:${b0} {
|
326 | findPerson: ${gr}'A query taking an ID, returns a Person'${dg},
|
327 | },
|
328 | ${b1}Mutation:${b0} {
|
329 | setPersonName: joinLines${gr}\`
|
330 | A mutation that sets the name of the user identified by an
|
331 | ID to the new name value supplied
|
332 | \`${dg}
|
333 | }
|
334 | }
|
335 | }
|
336 | \x1b[22;31m
|
337 | Note the usage of \`Person\`, \`Query\` and \`Mutation\` explicitly
|
338 | as keys to the supplied \`docs\` object.\x1b[0m
|
339 | `))
|
340 | }
|
341 |
|
342 | if (!results.valid) {
|
343 | let errorStrings = []
|
344 |
|
345 | for (let error of results.errors) {
|
346 | let { message, stack } = error
|
347 |
|
348 | stack = stack
|
349 | .trim()
|
350 | .split('\n')
|
351 | .splice(message.split('\n').length)
|
352 | .map(s => s.trim())
|
353 | .join('\n')
|
354 | message = message.replace(/(Error:\s)/, '$1\n').trim()
|
355 |
|
356 | errorStrings.push(
|
357 | `\x1b[31;1m${message}\x1b[0m\n` + indent(stack)
|
358 | )
|
359 | }
|
360 |
|
361 | let error = new Error(customDedent({dropLowest:true})`
|
362 | OOPS!
|
363 |
|
364 | An error occurred validating your factory template. The object
|
365 | in question is as follows:
|
366 |
|
367 | @template
|
368 |
|
369 | The individual errors that occurred are:
|
370 | \n@errors`
|
371 | .replace(/@template/, indent(_i(template)))
|
372 | .replace(/@errors/, errorStrings.join('\n\n'))
|
373 | )
|
374 |
|
375 | error.stack = error.message
|
376 | error.message = ''
|
377 |
|
378 | if (!hide) throw error
|
379 | }
|
380 |
|
381 | return results
|
382 | }
|
383 |
|
384 | |
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | static generateClass(name: string, baseClass: Function = GQLBase) {
|
397 | if (!name) {
|
398 | throw new Error('LatticeFactory.generateClass needs a name!!')
|
399 | }
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 | let fn = eval(`(new Function(
|
425 | "${baseClass.name}",
|
426 | "class ${name} extends ${baseClass.name} {}; return ${name};"
|
427 | ))`);
|
428 |
|
429 | let Class = fn(baseClass)
|
430 |
|
431 | this.brandClass(Class)
|
432 | newChecklist(Class)
|
433 |
|
434 | return Class;
|
435 | }
|
436 |
|
437 | |
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 | static injectSchema(Class: Function, schema: string) {
|
449 | if (!Class || !hasChecklist(Class)) {
|
450 | throw new Error(customDedent({dropLowest:true})`
|
451 | Either the supplied schema string is invalid
|
452 | SCHEMA: \`
|
453 | ${schema}
|
454 | \`
|
455 |
|
456 | Or your supplied class ${(Class && Class.name) || 'undefined'} is
|
457 | non-existent. Please check your code and try the LatticeFactory
|
458 | again.
|
459 | `)
|
460 | }
|
461 |
|
462 |
|
463 | Object.defineProperty(Class, 'SCHEMA', {
|
464 | get() { return schema }
|
465 | })
|
466 |
|
467 | setChecklist(Class, CHECK_SCHEMA)
|
468 |
|
469 | return Class
|
470 | }
|
471 |
|
472 | |
473 |
|
474 |
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 |
|
488 |
|
489 |
|
490 |
|
491 |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 | static injectResolvers(Class: Function, resolvers: Object): Function {
|
499 | if (!hasChecklist(Class, CHECK_SCHEMA)) {
|
500 | throw new Error(customDedent({dropLowest:true})`
|
501 | \`injectResolvers\` cannot be called on a class without a SCHEMA.
|
502 | Please verify your progress in the process and try again.
|
503 | `)
|
504 | }
|
505 |
|
506 | let tree = SyntaxTree.from(Class.SCHEMA)
|
507 | let outline = tree ? tree.outline : {}
|
508 |
|
509 | if (Class.name in outline && Class.name in resolvers) {
|
510 | let fields = Object.keys(outline[Class.name])
|
511 |
|
512 | for (let fieldResolver of fields) {
|
513 | if (!fieldResolver in resolvers[Class.name]) {
|
514 | LL.warn(customDedent({dropLowest: true})`
|
515 | ${fieldResolver} not supplied in resolvers for ${Class.name}
|
516 | `)
|
517 | continue;
|
518 | }
|
519 |
|
520 | let prop = resolvers[Class.name][fieldResolver]
|
521 |
|
522 | if (prop && typeof prop === 'function') {
|
523 | LL.info('Injecting [fn] %s', fieldResolver)
|
524 | Object.defineProperty(Class.prototype, fieldResolver, {
|
525 | value: prop
|
526 | })
|
527 | }
|
528 | else {
|
529 | LL.info('Injecting [prop] %s', fieldResolver)
|
530 | Properties(fieldResolver)(Class, ['factory-props'])
|
531 | }
|
532 | }
|
533 | }
|
534 |
|
535 | for (let [type: string, decorator: Function] of [
|
536 | ['Query', resolver],
|
537 | ['Mutation', mutator],
|
538 | ['Subscription', subscriptor]
|
539 | ]) {
|
540 | let keys = Object.keys(outline[type] || {})
|
541 |
|
542 |
|
543 | if (!type in outline || !keys.length) { continue; }
|
544 |
|
545 | for (let fnName of keys) {
|
546 | let fn = resolvers[fnName]
|
547 | decorator(Class, fnName, {value: fn})
|
548 | LL.info('Adding %s resolver [%s]', type, fnName)
|
549 | }
|
550 | }
|
551 |
|
552 | setChecklist(Class, CHECK_RESOLVERS)
|
553 |
|
554 | return Class
|
555 | }
|
556 |
|
557 | static injectDocs(Class: Function, docs: Object): Function {
|
558 | if (!hasChecklist(Class, CHECK_SCHEMA, CHECK_RESOLVERS)) {
|
559 | throw new Error(customDedent({dropLowest:true})`
|
560 | \`injectDocs\` cannot be called on a class without a SCHEMA or
|
561 | RESOLVERS defined. Please verify your progress in the process and try
|
562 | again.
|
563 | `)
|
564 | }
|
565 |
|
566 | let copyProp = (
|
567 | o: mixed,
|
568 | prop: string | Symbol,
|
569 | to: mixed,
|
570 | as: ?(string | Symbol)
|
571 | ): mixed => {
|
572 |
|
573 | let prototype = o.prototype || Object.getPrototypeOf(o)
|
574 | let descriptor = Object.getOwnPropertyDescriptor(prototype, prop)
|
575 |
|
576 | if (!as) {
|
577 | as = prop
|
578 | }
|
579 |
|
580 | if (descriptor) {
|
581 | Object.defineProperty(to, as, descriptor)
|
582 | }
|
583 | else {
|
584 |
|
585 | to[as] = o[prop]
|
586 | }
|
587 | }
|
588 |
|
589 |
|
590 |
|
591 | let result = {}
|
592 |
|
593 |
|
594 | const { TYPE } = this;
|
595 | const {
|
596 | DOC_CLASS, DOC_FIELDS, DOC_QUERY, DOC_MUTATION, DOC_SUBSCRIPTION,
|
597 | DOC_QUERIES, DOC_MUTATIONS, DOC_SUBSCRIPTIONS
|
598 | } = GQLBase
|
599 |
|
600 |
|
601 |
|
602 |
|
603 |
|
604 | for (let [Type, TopLevelConstant, FieldConstants] of [
|
605 | ['Query', DOC_QUERY, DOC_QUERIES],
|
606 | ['Mutation', DOC_MUTATION, DOC_MUTATIONS],
|
607 | ['Subscription', DOC_SUBSCRIPTION, DOC_SUBSCRIPTIONS],
|
608 | [Class.name, DOC_CLASS, DOC_FIELDS]
|
609 | ]) {
|
610 |
|
611 | if (docs[Type]) {
|
612 |
|
613 |
|
614 | if (docs[Type][TYPE]) {
|
615 | copyProp(docs[Type], TYPE, result, TopLevelConstant)
|
616 | }
|
617 |
|
618 |
|
619 |
|
620 |
|
621 | let entries = Object.entries(docs[Type])
|
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 |
|
628 | if (entries.length) {
|
629 | result[FieldConstants] = {}
|
630 | }
|
631 |
|
632 |
|
633 |
|
634 | for (let [prop, value] of entries) {
|
635 | copyProp(docs[Type], prop, result[FieldConstants])
|
636 | }
|
637 | }
|
638 | }
|
639 |
|
640 | Object.defineProperty(Class, 'apiDocs', {
|
641 | value: function() { return result }
|
642 | })
|
643 |
|
644 | setChecklist(Class, CHECK_API_DOCS)
|
645 |
|
646 | return Class
|
647 | }
|
648 |
|
649 | static build(template: Object): Function {
|
650 | let validationResults = this.validateTemplate(template)
|
651 | let Class = this.generateClass(template.name, template.type || GQLBase)
|
652 |
|
653 | if (!Class) {
|
654 | throw new Error(customDedent({dropLowest: true})`
|
655 | LatticeFactory was unable to build your Class from the name and types
|
656 | supplied in your template. You provided the following template. Please
|
657 | look it over and correct any errors before trying again.
|
658 |
|
659 | \x1b[1mTemplate\x1b[0m
|
660 | ${_i(template)}
|
661 | `)
|
662 | }
|
663 |
|
664 | this.injectSchema(Class, template.schema)
|
665 | this.injectResolvers(Class, template.resolvers || {})
|
666 | this.injectDocs(Class, template.docs || {})
|
667 |
|
668 |
|
669 | new Class({})
|
670 |
|
671 | if (!hasChecklist(Class, CHECK_SCHEMA, CHECK_RESOLVERS, CHECK_API_DOCS)) {
|
672 | let _schema = hasChecklist(Class, CHECK_SCHEMA) ? '✅' : '❌'
|
673 | let _resolvers = hasChecklist(Class, CHECK_RESOLVERS) ? '✅' : '❌'
|
674 | let _apiDocs = hasChecklist(Class, CHECK_API_DOCS) ? '✅' : '❌'
|
675 |
|
676 | throw new Error(customDedent({dropLowest: true})`
|
677 | Something went wrong in the process of building the class called
|
678 | ${Class && Class.name || template && template.name || 'Unknown!'},
|
679 | please check the supplied template for errors.
|
680 |
|
681 | [ ${_schema} ] Has a SCHEMA defined
|
682 | [ ${_resolvers} ] Has defined RESOLVERS matching the SCHEMA
|
683 | [ ${_apiDocs} ] Has defined API Docs matching the SCHEMA
|
684 |
|
685 | \x1b[1mTemplate\x1b[0m
|
686 | ${_i(template)}
|
687 |
|
688 | \x1b[1mClass\x1b[0m
|
689 | ${_i(Class)}
|
690 | `)
|
691 | }
|
692 |
|
693 | return Class
|
694 | }
|
695 |
|
696 | |
697 |
|
698 |
|
699 |
|
700 |
|
701 |
|
702 |
|
703 |
|
704 |
|
705 |
|
706 |
|
707 |
|
708 |
|
709 | static brandClass(Class: ?Function): ?Function {
|
710 | if (Class) {
|
711 | if (extendsFrom(Class, GQLBase)) {
|
712 | Class[META_KEY][this.FACTORY_CLASS] = true
|
713 | }
|
714 | else {
|
715 | Class[this.FACTORY_CLASS] = true
|
716 | }
|
717 | }
|
718 |
|
719 | return Class
|
720 | }
|
721 |
|
722 | |
723 |
|
724 |
|
725 |
|
726 |
|
727 |
|
728 |
|
729 |
|
730 |
|
731 |
|
732 |
|
733 |
|
734 |
|
735 |
|
736 | static isFactoryClass(Class: Function): boolean {
|
737 | if (Class) {
|
738 | return (extendsFrom(Class, GQLBase)
|
739 | ? !!Class[META_KEY][this.FACTORY_CLASS]
|
740 | : !!Class[this.FACTORY_CLASS]
|
741 | )
|
742 | }
|
743 |
|
744 | return false
|
745 | }
|
746 |
|
747 | |
748 |
|
749 |
|
750 |
|
751 |
|
752 |
|
753 |
|
754 |
|
755 |
|
756 |
|
757 |
|
758 |
|
759 |
|
760 | static removeClassBrand(Class: Function): Function {
|
761 | if (Class) {
|
762 | if (extendsFrom(Class, GQLBase)) {
|
763 | delete Class[META_KEY][this.FACTORY_CLASS]
|
764 | }
|
765 | else {
|
766 | delete Class[this.FACTORY_CLASS]
|
767 | }
|
768 | }
|
769 |
|
770 | return Class
|
771 | }
|
772 |
|
773 | |
774 |
|
775 |
|
776 |
|
777 |
|
778 |
|
779 |
|
780 |
|
781 |
|
782 |
|
783 | static get [Symbol.toStringTag]() { return this.name }
|
784 |
|
785 | |
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 |
|
792 |
|
793 | static get TYPE(): Symbol { return Symbol.for('API Docs Type Constant') }
|
794 |
|
795 | |
796 |
|
797 |
|
798 |
|
799 |
|
800 |
|
801 |
|
802 |
|
803 | static get FACTORY_CLASS(): Symbol { return Symbol.for('Factory Class') }
|
804 | }
|
805 |
|
806 | export const isFactoryClass = LatticeFactory.isFactoryClass
|
807 |
|
808 |
|
809 |
|
810 |
|
811 |
|
812 |
|
813 |
|
814 |
|
815 |
|
816 |
|