Skip to content

Commit cc6f1fd

Browse files
committed
feat: decorator support
1 parent 87c6683 commit cc6f1fd

File tree

15 files changed

+405
-10
lines changed

15 files changed

+405
-10
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"postinstall": "node ./dist/postinstall || exit 0"
1919
},
2020
"peerDependencies": {
21-
"@types/graphql": "^14.0.7",
21+
"@types/graphql": "^14.1.1",
2222
"graphql": "^14.1.1"
2323
},
2424
"dependencies": {
@@ -33,9 +33,10 @@
3333
"tslib": "^1.9.3"
3434
},
3535
"devDependencies": {
36+
"@apollo/federation": "^0.6.8",
3637
"@types/express": "^4.16.1",
3738
"@types/express-graphql": "^0.6.2",
38-
"@types/graphql": "^14.0.7",
39+
"@types/graphql": "^14.1.1",
3940
"@types/gulp": "^4.0.6",
4041
"@types/gulp-replace": "0.0.31",
4142
"@types/gulp-shell": "0.0.31",

src/decorators/Directive.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { MethodAndPropDecorator } from "./types";
2+
import { SymbolKeysNotSupportedError } from "../errors";
3+
import { getMetadataStorage } from "../metadata/getMetadataStorage";
4+
5+
export interface DirectiveArgs {
6+
[arg: string]: any;
7+
}
8+
9+
export function Directive(name: string, args?: DirectiveArgs): ClassDecorator;
10+
export function Directive(name: string, args?: DirectiveArgs): MethodAndPropDecorator;
11+
export function Directive(
12+
name: string,
13+
args?: DirectiveArgs,
14+
): MethodDecorator | PropertyDecorator | ClassDecorator {
15+
return (targetOrPrototype, propertyKey, descriptor) => {
16+
if (!propertyKey) {
17+
getMetadataStorage().collectDirectiveClassMetadata({
18+
target: targetOrPrototype as Function,
19+
name,
20+
args,
21+
});
22+
23+
return;
24+
}
25+
26+
if (typeof propertyKey === "symbol") {
27+
throw new SymbolKeysNotSupportedError();
28+
}
29+
30+
getMetadataStorage().collectDirectiveFieldMetadata({
31+
target: targetOrPrototype.constructor,
32+
field: propertyKey,
33+
name,
34+
args,
35+
});
36+
};
37+
}

src/decorators/ObjectType.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getMetadataStorage } from "../metadata/getMetadataStorage";
22
import { getNameDecoratorParams } from "../helpers/decorators";
33
import { DescriptionOptions, AbstractClassOptions } from "./types";
4+
import { ObjectClassMetadata } from "../metadata/definitions/object-class-metdata";
45

56
export type ObjectOptions = DescriptionOptions &
67
AbstractClassOptions & {

src/decorators/federation.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { MethodAndPropDecorator } from "./types";
2+
import { Directive } from "./Directive";
3+
4+
export interface FieldOptions {
5+
fields: string;
6+
}
7+
8+
export function FederationExtends(): ClassDecorator {
9+
return Directive("extends");
10+
}
11+
12+
export function FederationKey(opts: FieldOptions): ClassDecorator {
13+
return Directive("key", opts);
14+
}
15+
16+
export function FederationExternal(): MethodAndPropDecorator;
17+
export function FederationExternal(): MethodDecorator | PropertyDecorator {
18+
return Directive("external") as MethodDecorator | PropertyDecorator;
19+
}
20+
21+
export function FederationRequires(opts: FieldOptions): MethodAndPropDecorator;
22+
export function FederationRequires(opts: FieldOptions): MethodDecorator | PropertyDecorator {
23+
return Directive("requires", opts) as MethodDecorator | PropertyDecorator;
24+
}
25+
26+
export function FederationProvides(opts: FieldOptions): MethodAndPropDecorator;
27+
export function FederationProvides(opts: FieldOptions): MethodDecorator | PropertyDecorator {
28+
return Directive("provides", opts) as MethodDecorator | PropertyDecorator;
29+
}

src/decorators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ export { Root } from "./Root";
2020
export { Subscription } from "./Subscription";
2121
export { createUnionType } from "./unions";
2222
export { UseMiddleware } from "./UseMiddleware";
23+
export * from "./federation";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { FieldMetadata } from "./field-metadata";
2+
import { DirectiveClassMetadata } from "./directive-metadata";
23

34
export interface ClassMetadata {
45
name: string;
56
target: Function;
67
fields?: FieldMetadata[];
78
description?: string;
89
isAbstract?: boolean;
10+
directives?: DirectiveClassMetadata[];
911
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface DirectiveMetadata {
2+
name: string;
3+
args?: { [key: string]: any };
4+
}
5+
6+
export type DirectiveClassMetadata = DirectiveMetadata & { target: Function };
7+
export type DirectiveFieldMetadata = DirectiveMetadata & { target: Function; field: string };

src/metadata/definitions/field-metadata.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { ParamMetadata } from "./param-metadata";
22
import { TypeValueThunk, TypeOptions } from "../../decorators/types";
33
import { Middleware } from "../../interfaces/Middleware";
44
import { Complexity } from "../../interfaces";
5+
import { DirectiveFieldMetadata } from "./directive-metadata";
56

67
export interface FieldMetadata {
78
target: Function;
@@ -15,4 +16,5 @@ export interface FieldMetadata {
1516
params?: ParamMetadata[];
1617
roles?: any[];
1718
middlewares?: Array<Middleware<any>>;
19+
directives?: DirectiveFieldMetadata[];
1820
}

src/metadata/metadata-storage.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
} from "./utils";
2424
import { ObjectClassMetadata } from "./definitions/object-class-metdata";
2525
import { InterfaceClassMetadata } from "./definitions/interface-class-metadata";
26+
import { DirectiveClassMetadata, DirectiveFieldMetadata } from "./definitions/directive-metadata";
2627

2728
export class MetadataStorage {
2829
queries: ResolverMetadata[] = [];
@@ -37,6 +38,8 @@ export class MetadataStorage {
3738
enums: EnumMetadata[] = [];
3839
unions: UnionMetadataWithSymbol[] = [];
3940
middlewares: MiddlewareMetadata[] = [];
41+
classDirectives: DirectiveClassMetadata[] = [];
42+
fieldDirectives: DirectiveFieldMetadata[] = [];
4043

4144
private resolverClasses: ResolverClassMetadata[] = [];
4245
private fields: FieldMetadata[] = [];
@@ -98,6 +101,13 @@ export class MetadataStorage {
98101
this.params.push(definition);
99102
}
100103

104+
collectDirectiveClassMetadata(definition: DirectiveClassMetadata) {
105+
this.classDirectives.push(definition);
106+
}
107+
collectDirectiveFieldMetadata(definition: DirectiveFieldMetadata) {
108+
this.fieldDirectives.push(definition);
109+
}
110+
101111
build() {
102112
// TODO: disable next build attempts
103113

@@ -147,8 +157,10 @@ export class MetadataStorage {
147157
middleware => middleware.target === field.target && middleware.fieldName === field.name,
148158
),
149159
);
160+
field.directives = this.findFieldDirectives(field.target, field.name);
150161
});
151162
def.fields = fields;
163+
def.directives = this.findClassDirectives(def.target);
152164
});
153165
}
154166

@@ -253,4 +265,15 @@ export class MetadataStorage {
253265
}
254266
return authorizedField.roles;
255267
}
268+
269+
private findClassDirectives(target: ClassMetadata["target"]): DirectiveClassMetadata[] {
270+
return this.classDirectives.filter(it => it.target === target);
271+
}
272+
273+
private findFieldDirectives(
274+
target: FieldMetadata["target"],
275+
name: FieldMetadata["name"],
276+
): DirectiveFieldMetadata[] {
277+
return this.fieldDirectives.filter(field => field.target === target && field.field === name);
278+
}
256279
}

src/schema/schema-generator.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import {
1515
GraphQLEnumValueConfigMap,
1616
GraphQLUnionType,
1717
GraphQLTypeResolver,
18+
GraphQLDirective,
19+
DirectiveNode,
20+
ObjectTypeDefinitionNode,
21+
FieldDefinitionNode,
22+
ArgumentNode,
1823
} from "graphql";
1924
import { withFilter, ResolverFn } from "graphql-subscriptions";
2025

@@ -43,6 +48,10 @@ import {
4348
import { ResolverFilterData, ResolverTopicData, TypeResolver } from "../interfaces";
4449
import { getFieldMetadataFromInputType, getFieldMetadataFromObjectType } from "./utils";
4550
import { ensureInstalledCorrectGraphQLPackage } from "../utils/graphql-version";
51+
import {
52+
DirectiveClassMetadata,
53+
DirectiveFieldMetadata,
54+
} from "../metadata/definitions/directive-metadata";
4655

4756
interface AbstractInfo {
4857
isAbstract: boolean;
@@ -73,6 +82,7 @@ export interface SchemaGeneratorOptions extends BuildContextOptions {
7382
* Disable checking on build the correctness of a schema
7483
*/
7584
skipCheck?: boolean;
85+
directives?: GraphQLDirective[];
7686
}
7787

7888
export abstract class SchemaGenerator {
@@ -104,6 +114,7 @@ export abstract class SchemaGenerator {
104114
mutation: this.buildRootMutationType(),
105115
subscription: this.buildRootSubscriptionType(),
106116
types: this.buildOtherTypes(),
117+
directives: options.directives,
107118
});
108119

109120
BuildContext.reset();
@@ -255,12 +266,66 @@ export abstract class SchemaGenerator {
255266
return superClassTypeInfo ? superClassTypeInfo.type : undefined;
256267
};
257268
const interfaceClasses = objectType.interfaceClasses || [];
269+
270+
const classDirectiveAstNodes = (
271+
name: string,
272+
classMetas?: DirectiveClassMetadata[],
273+
): ObjectTypeDefinitionNode | undefined => {
274+
if (!classMetas || !classMetas.length) {
275+
return;
276+
}
277+
278+
const directives: DirectiveNode[] = classMetas.map(meta =>
279+
this.createDirective(meta.name, meta.args),
280+
);
281+
282+
return {
283+
kind: "ObjectTypeDefinition",
284+
name: {
285+
kind: "Name",
286+
value: name,
287+
},
288+
interfaces: [],
289+
directives,
290+
};
291+
};
292+
293+
const fieldDirectiveAstNodes = (
294+
name: string,
295+
fieldMetas?: DirectiveFieldMetadata[],
296+
): FieldDefinitionNode | undefined => {
297+
if (!fieldMetas || !fieldMetas.length) {
298+
return;
299+
}
300+
301+
const directives: DirectiveNode[] = fieldMetas.map(meta =>
302+
this.createDirective(meta.name, meta.args),
303+
);
304+
305+
return {
306+
kind: "FieldDefinition",
307+
type: {
308+
kind: "NamedType",
309+
name: {
310+
kind: "Name",
311+
value: name,
312+
},
313+
},
314+
name: {
315+
kind: "Name",
316+
value: name,
317+
},
318+
directives,
319+
};
320+
};
321+
258322
return {
259323
target: objectType.target,
260324
isAbstract: objectType.isAbstract || false,
261325
type: new GraphQLObjectType({
262326
name: objectType.name,
263327
description: objectType.description,
328+
astNode: classDirectiveAstNodes(objectType.name, objectType.directives),
264329
interfaces: () => {
265330
let interfaces = interfaceClasses.map<GraphQLInterfaceType>(
266331
interfaceClass =>
@@ -295,6 +360,7 @@ export abstract class SchemaGenerator {
295360
: createSimpleFieldResolver(field),
296361
description: field.description,
297362
deprecationReason: field.deprecationReason,
363+
astNode: fieldDirectiveAstNodes(field.name, field.directives),
298364
};
299365
return fieldsMap;
300366
},
@@ -604,4 +670,34 @@ export abstract class SchemaGenerator {
604670
return this.objectTypesInfo.find(objectType => objectType.target === resolvedType)!.type;
605671
};
606672
}
673+
674+
private static createDirective(name: string, args?: { [name: string]: any }): DirectiveNode {
675+
const directiveArguments: ArgumentNode[] = [];
676+
677+
if (args) {
678+
Object.keys(args).forEach(arg => {
679+
directiveArguments.push({
680+
kind: "Argument",
681+
name: {
682+
kind: "Name",
683+
value: arg,
684+
},
685+
value: {
686+
kind: "StringValue",
687+
value: args[arg],
688+
block: false,
689+
},
690+
});
691+
});
692+
}
693+
694+
return {
695+
kind: "Directive",
696+
name: {
697+
kind: "Name",
698+
value: name,
699+
},
700+
arguments: directiveArguments,
701+
};
702+
}
607703
}

0 commit comments

Comments
 (0)