Skip to content

Commit 46b5ef3

Browse files
committed
feat(graphql): Add registerIn option for module-scoped type filtering
Add the ability to associate GraphQL types with specific NestJS modules using the new `registerIn` option. This enables type filtering when generating schemas for specific modules via the `include` option. ## Motivation When building multiple GraphQL endpoints with different schemas, developers need a way to control which types appear in each schema. Previously, only resolvers could be filtered by module - types were always included globally. ## Changes - Add `registerIn` option to decorators: - @ObjectType, @inputType, @InterfaceType, @ArgsType - registerEnumType(), createUnionType() - Add `RegisterInOption` type (Function | (() => Function)) - Add module filtering logic in TypeMetadataStorage - Add TypeDefinitionsStorage.clear() for multi-schema generation - Connect GqlModuleOptions.include to type filtering ## Behavior - Types without `registerIn`: Included in all schemas - Types with `registerIn`: Included only when module is in `include` list ## Example ```typescript @ObjectType({ registerIn: () => UsersModule }) class User { ... } ``` ## Notes I followed @kamilmysliwiec's suggestion from #1432 (comment) and took over #1999 since it seems no progress recently. If you suggest any other solutions please let me know. ## Fixes #1999, #14759
1 parent f8a1b5c commit 46b5ef3

17 files changed

+1319
-31
lines changed

packages/graphql/lib/decorators/args-type.decorator.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,47 @@
11
import { ClassType } from '../enums/class-type.enum';
2+
import { RegisterInOption } from '../schema-builder/metadata';
23
import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
34
import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
45
import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
56

7+
/**
8+
* Interface defining options that can be passed to `@ArgsType()` decorator.
9+
*
10+
* @publicApi
11+
*/
12+
export interface ArgsTypeOptions {
13+
/**
14+
* NestJS module that this type belongs to.
15+
* When specified, this type will only be included in GraphQL schemas
16+
* that include this module via the `include` option.
17+
* @see RegisterInOption for details
18+
*/
19+
registerIn?: RegisterInOption;
20+
}
21+
22+
/**
23+
* Decorator that marks a class as a resolver arguments type.
24+
*
25+
* @publicApi
26+
*/
27+
export function ArgsType(): ClassDecorator;
28+
/**
29+
* Decorator that marks a class as a resolver arguments type.
30+
*
31+
* @publicApi
32+
*/
33+
export function ArgsType(options: ArgsTypeOptions): ClassDecorator;
634
/**
735
* Decorator that marks a class as a resolver arguments type.
836
*
937
* @publicApi
1038
*/
11-
export function ArgsType(): ClassDecorator {
39+
export function ArgsType(options?: ArgsTypeOptions): ClassDecorator {
1240
return (target: Function) => {
1341
const metadata = {
1442
name: target.name,
1543
target,
44+
registerIn: options?.registerIn,
1645
};
1746
LazyMetadataStorage.store(() =>
1847
TypeMetadataStorage.addArgsMetadata(metadata),

packages/graphql/lib/decorators/input-type.decorator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { isString } from '@nestjs/common/utils/shared.utils';
99
import { ClassType } from '../enums/class-type.enum';
10+
import { RegisterInOption } from '../schema-builder/metadata';
1011
import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
1112
import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
1213
import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
@@ -30,6 +31,13 @@ export interface InputTypeOptions {
3031
* More info about '@oneOf' types in the [GraphQL spec](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects).
3132
*/
3233
isOneOf?: boolean;
34+
/**
35+
* NestJS module that this type belongs to.
36+
* When specified, this type will only be included in GraphQL schemas
37+
* that include this module via the `include` option.
38+
* @see RegisterInOption for details
39+
*/
40+
registerIn?: RegisterInOption;
3341
}
3442

3543
/**
@@ -73,6 +81,7 @@ export function InputType(
7381
description: options.description,
7482
isAbstract: options.isAbstract,
7583
isOneOf: options.isOneOf,
84+
registerIn: options.registerIn,
7685
};
7786
LazyMetadataStorage.store(() =>
7887
TypeMetadataStorage.addInputTypeMetadata(metadata),

packages/graphql/lib/decorators/interface-type.decorator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { isString } from '@nestjs/common/utils/shared.utils';
99
import { ClassType } from '../enums/class-type.enum';
1010
import { ResolveTypeFn } from '../interfaces';
11+
import { RegisterInOption } from '../schema-builder/metadata';
1112
import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
1213
import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
1314
import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
@@ -34,6 +35,13 @@ export interface InterfaceTypeOptions {
3435
* Interfaces implemented by this interface.
3536
*/
3637
implements?: Function | Function[] | (() => Function | Function[]);
38+
/**
39+
* NestJS module that this type belongs to.
40+
* When specified, this type will only be included in GraphQL schemas
41+
* that include this module via the `include` option.
42+
* @see RegisterInOption for details
43+
*/
44+
registerIn?: RegisterInOption;
3745
}
3846

3947
/**
@@ -71,6 +79,7 @@ export function InterfaceType(
7179
target,
7280
...options,
7381
interfaces: options.implements,
82+
registerIn: options.registerIn,
7483
};
7584
TypeMetadataStorage.addInterfaceMetadata(metadata);
7685
};

packages/graphql/lib/decorators/object-type.decorator.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { isString } from '@nestjs/common/utils/shared.utils';
99
import { ClassType } from '../enums/class-type.enum';
10+
import { RegisterInOption } from '../schema-builder/metadata';
1011
import { LazyMetadataStorage } from '../schema-builder/storages/lazy-metadata.storage';
1112
import { TypeMetadataStorage } from '../schema-builder/storages/type-metadata.storage';
1213
import { addClassTypeMetadata } from '../utils/add-class-type-metadata.util';
@@ -34,6 +35,13 @@ export interface ObjectTypeOptions {
3435
* Also works on classes marked with `isAbstract: true`.
3536
*/
3637
inheritDescription?: boolean;
38+
/**
39+
* NestJS module that this type belongs to.
40+
* When specified, this type will only be included in GraphQL schemas
41+
* that include this module via the `include` option.
42+
* @see RegisterInOption for details
43+
*/
44+
registerIn?: RegisterInOption;
3745
}
3846

3947
/**
@@ -85,6 +93,7 @@ export function ObjectType(
8593
interfaces: options.implements,
8694
isAbstract: options.isAbstract,
8795
inheritDescription: options.inheritDescription,
96+
registerIn: options.registerIn,
8897
});
8998

9099
// This function must be called eagerly to allow resolvers

packages/graphql/lib/graphql-schema.builder.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ export class GraphQLSchemaBuilder {
4343
) ?? true,
4444
),
4545
],
46+
// Pass include modules for type filtering in Code First approach
47+
includeModules: options.include,
4648
},
4749
options.sortSchema,
4850
options.transformAutoSchemaFile && options.transformSchema,

packages/graphql/lib/schema-builder/graphql-schema.factory.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ import { LazyMetadataStorage } from './storages/lazy-metadata.storage';
2323
import { TypeMetadataStorage } from './storages/type-metadata.storage';
2424
import { TypeDefinitionsGenerator } from './type-definitions.generator';
2525

26+
/**
27+
* Internal options interface that extends BuildSchemaOptions with includeModules.
28+
* This is used internally by GraphQLSchemaBuilder to pass module filtering options.
29+
* @internal
30+
*/
31+
interface InternalBuildSchemaOptions extends BuildSchemaOptions {
32+
includeModules?: Function[];
33+
}
34+
2635
@Injectable()
2736
export class GraphQLSchemaFactory {
2837
private readonly logger = new Logger(GraphQLSchemaFactory.name);
@@ -63,7 +72,12 @@ export class GraphQLSchemaFactory {
6372
LazyMetadataStorage.load(resolvers);
6473
TypeMetadataStorage.compile(options.orphanedTypes);
6574

66-
this.typeDefinitionsGenerator.generate(options);
75+
// includeModules is passed internally from GraphQLSchemaBuilder
76+
const internalOptions = options as InternalBuildSchemaOptions;
77+
this.typeDefinitionsGenerator.generate(
78+
options,
79+
internalOptions.includeModules,
80+
);
6781

6882
const schema = new GraphQLSchema({
6983
mutation: this.mutationTypeFactory.create(resolvers, options),

packages/graphql/lib/schema-builder/metadata/class.metadata.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
import { DirectiveMetadata } from './directive.metadata';
22
import { PropertyMetadata } from './property.metadata';
33

4+
/**
5+
* Type for the registerIn option used in GraphQL type decorators.
6+
* Can be a module class directly or a function that returns the module class
7+
* (useful to avoid circular dependency issues).
8+
*
9+
* @publicApi
10+
*/
11+
export type RegisterInOption = Function | (() => Function);
12+
413
export interface ClassMetadata {
514
target: Function;
615
name: string;
@@ -11,4 +20,9 @@ export interface ClassMetadata {
1120
properties?: PropertyMetadata[];
1221
inheritDescription?: boolean;
1322
isOneOf?: boolean; // For '@oneOf' input types
23+
/**
24+
* NestJS module that this type belongs to.
25+
* @see RegisterInOption for details
26+
*/
27+
registerIn?: RegisterInOption;
1428
}

packages/graphql/lib/schema-builder/metadata/enum.metadata.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { RegisterInOption } from './class.metadata';
2+
13
export interface EnumMetadataValuesMapOptions {
24
deprecationReason?: string;
35
description?: string;
@@ -12,4 +14,9 @@ export interface EnumMetadata<T extends object = any> {
1214
name: string;
1315
description?: string;
1416
valuesMap?: EnumMetadataValuesMap<T>;
17+
/**
18+
* NestJS module that this enum belongs to.
19+
* @see RegisterInOption for details
20+
*/
21+
registerIn?: RegisterInOption;
1522
}

packages/graphql/lib/schema-builder/metadata/union.metadata.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Type } from '@nestjs/common';
22
import { ResolveTypeFn } from '../../interfaces';
3+
import { RegisterInOption } from './class.metadata';
34

45
export interface UnionMetadata<
56
T extends readonly Type<unknown>[] = readonly Type<unknown>[],
@@ -9,4 +10,9 @@ export interface UnionMetadata<
910
id?: symbol;
1011
description?: string;
1112
resolveType?: ResolveTypeFn;
13+
/**
14+
* NestJS module that this union belongs to.
15+
* @see RegisterInOption for details
16+
*/
17+
registerIn?: RegisterInOption;
1218
}

packages/graphql/lib/schema-builder/storages/type-definitions.storage.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,18 @@ export class TypeDefinitionsStorage {
150150
}
151151
return;
152152
}
153+
154+
/**
155+
* Clear all type definitions.
156+
* Used when generating multiple schemas with different module configurations.
157+
*/
158+
clear() {
159+
this.interfaceTypeDefinitions.clear();
160+
this.enumTypeDefinitions.clear();
161+
this.unionTypeDefinitions.clear();
162+
this.objectTypeDefinitions.clear();
163+
this.inputTypeDefinitions.clear();
164+
this.inputTypeDefinitionsLinks = undefined;
165+
this.outputTypeDefinitionsLinks = undefined;
166+
}
153167
}

0 commit comments

Comments
 (0)