@@ -78,6 +78,11 @@ export interface IGraphQLComponent<TContextType extends ComponentContext = Compo
7878 federation ?: boolean ;
7979}
8080
81+ /**
82+ * GraphQLComponent class for building modular GraphQL schemas
83+ * @template TContextType - The type of the context object
84+ * @implements {IGraphQLComponent}
85+ */
8186export default class GraphQLComponent < TContextType extends ComponentContext = ComponentContext > implements IGraphQLComponent {
8287 _schema : GraphQLSchema ;
8388 _types : TypeSource ;
@@ -92,6 +97,7 @@ export default class GraphQLComponent<TContextType extends ComponentContext = Co
9297 _federation : boolean ;
9398 _dataSourceContextInject : DataSourceInjectionFunction ;
9499 _transforms : SchemaMapper [ ]
100+ private _transformedSchema : GraphQLSchema ;
95101
96102 constructor ( {
97103 types,
@@ -169,14 +175,18 @@ export default class GraphQLComponent<TContextType extends ComponentContext = Co
169175 return ctx as TContextType ;
170176 } ;
171177
178+ this . validateConfig ( { types, imports, mocks, federation } ) ;
179+
172180 }
173181
174182 get context ( ) : IContextWrapper {
175183
176- const contextFn = async ( context ) : Promise < ComponentContext > => {
184+ const contextFn = async ( context : Record < string , unknown > ) : Promise < ComponentContext > => {
177185 debug ( `building root context` ) ;
178-
179- for ( const { name, fn } of contextFn . _middleware ) {
186+
187+ const middleware : MiddlewareEntry [ ] = ( contextFn as any ) . _middleware || [ ] ;
188+
189+ for ( const { name, fn } of middleware ) {
180190 debug ( `applying ${ name } middleware` ) ;
181191 context = await fn ( context ) ;
182192 }
@@ -212,74 +222,76 @@ export default class GraphQLComponent<TContextType extends ComponentContext = Co
212222 }
213223
214224 get schema ( ) : GraphQLSchema {
215- if ( this . _schema ) {
216- return this . _schema ;
217- }
218-
219- let makeSchema : ( schemaConfig : any ) => GraphQLSchema = undefined ;
225+ try {
226+ if ( this . _schema ) {
227+ return this . _schema ;
228+ }
220229
221- if ( this . _federation ) {
222- makeSchema = ( schemaConfig ) : GraphQLSchema => {
223- return buildFederatedSchema ( schemaConfig ) ;
224- } ;
225- }
226- else {
227- makeSchema = makeExecutableSchema ;
228- }
230+ let makeSchema : ( schemaConfig : any ) => GraphQLSchema ;
229231
230- if ( this . _imports . length > 0 ) {
231- // iterate through the imports and construct subschema configuration objects
232- const subschemas = this . _imports . map ( ( imp ) => {
233- const { component, configuration = { } } = imp ;
232+ if ( this . _federation ) {
233+ makeSchema = buildFederatedSchema ;
234+ } else {
235+ makeSchema = makeExecutableSchema ;
236+ }
234237
235- return {
236- schema : component . schema ,
237- ... configuration
238- } ;
239- } ) ;
240-
241- // construct an aggregate schema from the schemas of imported
242- // components and this component's types/resolvers (if present)
243- this . _schema = stitchSchemas ( {
244- subschemas ,
245- typeDefs : this . _types ,
246- resolvers : this . _resolvers ,
247- mergeDirectives : true
248- } ) ;
249- }
250- else {
251- const schemaConfig = {
252- typeDefs : mergeTypeDefs ( this . _types ) ,
253- resolvers : this . _resolvers
238+ if ( this . _imports . length > 0 ) {
239+ // iterate through the imports and construct subschema configuration objects
240+ const subschemas = this . _imports . map ( ( imp ) => {
241+ const { component , configuration = { } } = imp ;
242+
243+ return {
244+ schema : component . schema ,
245+ ... configuration
246+ } ;
247+ } ) ;
248+
249+ // construct an aggregate schema from the schemas of imported
250+ // components and this component's types/resolvers (if present)
251+ this . _schema = stitchSchemas ( {
252+ subschemas ,
253+ typeDefs : this . _types ,
254+ resolvers : this . _resolvers ,
255+ mergeDirectives : true
256+ } ) ;
254257 }
258+ else {
259+ const schemaConfig = {
260+ typeDefs : mergeTypeDefs ( this . _types ) ,
261+ resolvers : this . _resolvers
262+ }
255263
256- this . _schema = makeSchema ( schemaConfig ) ;
257- }
264+ this . _schema = makeSchema ( schemaConfig ) ;
265+ }
258266
259- if ( this . _transforms ) {
260- this . _schema = transformSchema ( this . _schema , this . _transforms ) ;
261- }
267+ if ( this . _transforms ) {
268+ this . _schema = this . transformSchema ( this . _schema , this . _transforms ) ;
269+ }
262270
263- if ( this . _mocks !== undefined && typeof this . _mocks === 'boolean' && this . _mocks === true ) {
264- debug ( `adding default mocks to the schema for ${ this . name } ` ) ;
265- // if mocks are a boolean support simply applying default mocks
266- this . _schema = addMocksToSchema ( { schema : this . _schema , preserveResolvers : true } ) ;
267- }
268- else if ( this . _mocks !== undefined && typeof this . _mocks === 'object' ) {
269- debug ( `adding custom mocks to the schema for ${ this . name } ` ) ;
270- // else if mocks is an object, that means the user provided
271- // custom mocks, with which we pass them to addMocksToSchema so they are applied
272- this . _schema = addMocksToSchema ( { schema : this . _schema , mocks : this . _mocks , preserveResolvers : true } ) ;
273- }
271+ if ( this . _mocks !== undefined && typeof this . _mocks === 'boolean' && this . _mocks === true ) {
272+ debug ( `adding default mocks to the schema for ${ this . name } ` ) ;
273+ // if mocks are a boolean support simply applying default mocks
274+ this . _schema = addMocksToSchema ( { schema : this . _schema , preserveResolvers : true } ) ;
275+ }
276+ else if ( this . _mocks !== undefined && typeof this . _mocks === 'object' ) {
277+ debug ( `adding custom mocks to the schema for ${ this . name } ` ) ;
278+ // else if mocks is an object, that means the user provided
279+ // custom mocks, with which we pass them to addMocksToSchema so they are applied
280+ this . _schema = addMocksToSchema ( { schema : this . _schema , mocks : this . _mocks , preserveResolvers : true } ) ;
281+ }
274282
275- if ( this . _pruneSchema ) {
276- debug ( `pruning the schema for ${ this . name } ` ) ;
277- this . _schema = pruneSchema ( this . _schema , this . _pruneSchemaOptions ) ;
278- }
283+ if ( this . _pruneSchema ) {
284+ debug ( `pruning the schema for ${ this . name } ` ) ;
285+ this . _schema = pruneSchema ( this . _schema , this . _pruneSchemaOptions ) ;
286+ }
279287
280- debug ( `created schema for ${ this . name } ` ) ;
288+ debug ( `created schema for ${ this . name } ` ) ;
281289
282- return this . _schema ;
290+ return this . _schema ;
291+ } catch ( error ) {
292+ debug ( `Error creating schema for ${ this . name } : ${ error } ` ) ;
293+ throw new Error ( `Failed to create schema for component ${ this . name } : ${ error . message } ` ) ;
294+ }
283295 }
284296
285297 get types ( ) : TypeSource {
@@ -310,6 +322,57 @@ export default class GraphQLComponent<TContextType extends ComponentContext = Co
310322 return this . _federation ;
311323 }
312324
325+ public dispose ( ) : void {
326+ this . _schema = null ;
327+ this . _types = null ;
328+ this . _resolvers = null ;
329+ this . _imports = null ;
330+ this . _dataSources = null ;
331+ this . _dataSourceOverrides = null ;
332+ }
333+
334+ private transformSchema ( schema : GraphQLSchema , transforms : SchemaMapper [ ] ) : GraphQLSchema {
335+ if ( this . _transformedSchema ) {
336+ return this . _transformedSchema ;
337+ }
338+
339+ const functions = { } ;
340+ const mapping = { } ;
341+
342+ for ( const transform of transforms ) {
343+ for ( const [ key , fn ] of Object . entries ( transform ) ) {
344+ if ( ! mapping [ key ] ) {
345+ functions [ key ] = [ ] ;
346+ let result = undefined ;
347+ mapping [ key ] = function ( ...args ) {
348+ while ( functions [ key ] . length ) {
349+ const mapper = functions [ key ] . shift ( ) ;
350+ result = mapper ( ...args ) ;
351+ if ( ! result ) {
352+ break ;
353+ }
354+ }
355+ return result ;
356+ }
357+ }
358+ functions [ key ] . push ( fn ) ;
359+ }
360+ }
361+
362+ this . _transformedSchema = mapSchema ( schema , mapping ) ;
363+ return this . _transformedSchema ;
364+ }
365+
366+ private validateConfig ( options : IGraphQLComponentOptions ) : void {
367+ if ( options . federation && ! options . types ) {
368+ throw new Error ( 'Federation requires type definitions' ) ;
369+ }
370+
371+ if ( options . mocks && typeof options . mocks !== 'boolean' && typeof options . mocks !== 'object' ) {
372+ throw new Error ( 'mocks must be either boolean or object' ) ;
373+ }
374+ }
375+
313376}
314377
315378/**
@@ -442,34 +505,7 @@ const bindResolvers = function (bindContext: IGraphQLComponent, resolvers: IReso
442505 return boundResolvers ;
443506} ;
444507
445- /**
446- * Transforms a schema using the provided transforms
447- * @param {GraphQLSchema } schema The schema to transform
448- * @param {SchemaMapper[] } transforms An array of schema transforms
449- * @returns {GraphQLSchema } The transformed schema
450- */
451- const transformSchema = function ( schema : GraphQLSchema , transforms : SchemaMapper [ ] ) {
452- const functions = { } ;
453- const mapping = { } ;
454-
455- for ( const transform of transforms ) {
456- for ( const [ key , fn ] of Object . entries ( transform ) ) {
457- if ( ! mapping [ key ] ) {
458- functions [ key ] = [ ] ;
459- mapping [ key ] = function ( arg ) {
460- while ( functions [ key ] . length ) {
461- const mapper = functions [ key ] . shift ( ) ;
462- arg = mapper ( arg ) ;
463- if ( ! arg ) {
464- break ;
465- }
466- }
467- return arg ;
468- }
469- }
470- functions [ key ] . push ( fn ) ;
471- }
472- }
473-
474- return mapSchema ( schema , mapping ) ;
508+ interface MiddlewareEntry {
509+ name : string ;
510+ fn : ContextFunction ;
475511}
0 commit comments