Skip to content

Commit

Permalink
feat(core): add initial support for directives
Browse files Browse the repository at this point in the history
Namely FederationKey for resolveReference decorator
  • Loading branch information
marcus-sa committed Feb 7, 2024
1 parent 883690d commit 73fd553
Show file tree
Hide file tree
Showing 16 changed files with 515 additions and 164 deletions.
13 changes: 0 additions & 13 deletions docs/concepts/resolvers.mdx

This file was deleted.

Empty file.
3 changes: 3 additions & 0 deletions docs/concepts/resolvers/fields.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@


### Parent
Empty file.
Empty file.
Empty file.
Empty file.
Empty file.
22 changes: 11 additions & 11 deletions docs/concepts/types/data.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ title: 'Data'

## ID
The [`ID`](https://deepkit.io/documentation/runtime-types/types#uuid) type is represented as a `ID` scalar.
<Info>`ID` is imported from `@deepkit-graphql/core`</Info>
<Info>`ID` is exported from `@deepkit-graphql/core`</Info>

## MongoID
The [`MongoID`](https://deepkit.io/documentation/runtime-types/types#mongoid) type is represented as a `ObjectID` scalar.
<Info>`MongoID` is imported from `@deepkit/type`</Info>
<Info>`MongoID` is exported from `@deepkit/type`</Info>

## UUID
The [`UUID`](https://deepkit.io/documentation/runtime-types/types#uuid) type is represented as a `UUID` scalar.
<Info>`UUID` is imported from `@deepkit/type`</Info>
<Info>`UUID` is exported from `@deepkit/type`</Info>

## Array
The `Array` type is represented as a `List`.
Expand All @@ -32,39 +32,39 @@ The `boolean` type is is represented as a `Boolean` scalar.

## float
The [`float`](https://deepkit.io/documentation/runtime-types/types#float) type is represented as a `Float` scalar.
<Info>`float` is imported from `@deepkit/type`</Info>
<Info>`float` is exported from `@deepkit/type`</Info>

### Annotations

#### Positive
The `Positive` annotation for `float` is represented as a [`PositiveFloat`](https://the-guild.dev/graphql/scalars/docs/scalars/positive-float) scalar.

<Info>`Positive` is imported from `@deepkit/type`</Info>
<Info>`Positive` is exported from `@deepkit/type`</Info>

#### Negative
The `Negative` annotation for `float` is represented as a [`NegativeFloat`](https://the-guild.dev/graphql/scalars/docs/scalars/negative-float) scalar.

<Info>`Negative` is imported from `@deepkit/type`</Info>
<Info>`Negative` is exported from `@deepkit/type`</Info>

## number
The `number` type is not a valid type, because it cannot be represented.

## integer
The [`integer`](https://deepkit.io/documentation/runtime-types/types#integer) type is represented as a [`Int`](https://graphql.com/learn/scalars-objects-lists/#the-int-type) scalar.

<Info>`integer` is imported from `@deepkit/type`</Info>
<Info>`integer` is exported from `@deepkit/type`</Info>

### Annotations

#### Positive
The [`Positive`](https://deepkit.io/documentation/runtime-types/types#integer) annotation for `integer` is represented as a [`PositiveInt`](https://the-guild.dev/graphql/scalars/docs/scalars/positive-int) scalar.

<Info>`Positive` is imported from `@deepkit/type`</Info>
<Info>`Positive` is exported from `@deepkit/type`</Info>

#### Negative
The `Negative` annotation for `integer` is represented as a [`NegativeInt`](https://the-guild.dev/graphql/scalars/docs/scalars/negative-int) scalar.

<Info>`Negative` is imported from `@deepkit/type`</Info>
<Info>`Negative` is exported from `@deepkit/type`</Info>

## bigint
The [`bigint`](https://deepkit.io/documentation/runtime-types/types#bigint) type is represented as a [`BigInt`](https://the-guild.dev/graphql/scalars/docs/scalars/big-int) scalar.
Expand All @@ -73,11 +73,11 @@ The [`bigint`](https://deepkit.io/documentation/runtime-types/types#bigint) type

#### BinaryBigInt
The [`BinaryBigInt`](https://deepkit.io/documentation/runtime-types/types#bigint) type is represented as a `BinaryBigInt` scalar.
<Info>`BinaryBigInt` is imported from `@deepkit/type`</Info>
<Info>`BinaryBigInt` is exported from `@deepkit/type`</Info>

#### SignedBinaryBigInt
The [`SignedBinaryBigInt`](https://deepkit.io/documentation/runtime-types/types#bigint) type is represented as a `SignedBinaryBigInt` scalar.
<Info>`SignedBinaryBigInt` is imported from `@deepkit/type`</Info>
<Info>`SignedBinaryBigInt` is exported from `@deepkit/type`</Info>

## ArrayBuffer
The [`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) type is represented as a `Byte` scalar.
Expand Down
4 changes: 2 additions & 2 deletions docs/concepts/types/utility.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## Intersection

## Excluded
<Info>`Excluded` is imported from `@deepkit/type`</Info>
<Info>`Excluded` is exported from `@deepkit/type`</Info>

## MapName
<Info>`MapName` is imported from `@deepkit/type`</Info>
<Info>`MapName` is exported from `@deepkit/type`</Info>
18 changes: 16 additions & 2 deletions docs/mint.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,26 @@
"concepts/types/validation"
]
},
"concepts/resolvers"
{
"group": "Resolvers",
"pages": [
"concepts/resolvers/overview",
"concepts/resolvers/queries",
"concepts/resolvers/mutations",
"concepts/resolvers/subscriptions",
"concepts/resolvers/fields",
"concepts/resolvers/middleware"
]
}
]
},
{
"group": "Integrations",
"pages": ["integrations/apollo", "integrations/yoga", "integrations/standalone"]
"pages": [
"integrations/apollo",
"integrations/yoga",
"integrations/standalone"
]
},
{
"group": "Migrations",
Expand Down
83 changes: 65 additions & 18 deletions packages/core/src/lib/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class GraphQLClassMetadata {
readonly mutations = new Map<string, GraphQLPropertyMetadata>();
readonly queries = new Map<string, GraphQLPropertyMetadata>();
readonly resolveFields = new Map<string, GraphQLPropertyMetadata>();
readonly resolveReferences = new Map<string, GraphQLPropertyMetadata>();
readonly subscriptions = new Map<string, GraphQLPropertyMetadata>();
readonly checks = new Set<(decorator: GraphQLClassDecorator) => void>();
}
Expand Down Expand Up @@ -87,6 +88,10 @@ export class GraphQLClassDecorator {
this.t.resolveFields.set(property, metadata);
}

addResolveReference(property: string, metadata: GraphQLPropertyMetadata) {
this.t.resolveReferences.set(property, metadata);
}

addCheck(check: (decorator: GraphQLClassDecorator) => void) {
this.t.checks.add(check);
}
Expand Down Expand Up @@ -121,12 +126,26 @@ interface GraphQLResolveFieldOptions {
deprecationReason?: string;
}

interface GraphQLResolveReferencedOptions {
name?: string;
description?: string;
deprecationReason?: string;
}

export enum GraphQLPropertyType {
QUERY,
MUTATION,
SUBSCRIPTION,
RESOLVE_FIELD,
RESOLVE_REFERENCE,
}

export class GraphQLPropertyMetadata implements GraphQLQueryOptions {
name: string;
property: string;
classType: ClassType;
middleware: Set<InternalMiddleware> = new Set<InternalMiddleware>();
type: 'query' | 'mutation' | 'subscription' | 'resolveField';
type: GraphQLPropertyType;
// TODO
// returnType: Type;
description?: string;
Expand All @@ -145,21 +164,23 @@ class GraphQLPropertyDecorator {

gqlClassDecorator.addCheck(() => {
switch (this.t.type) {
case 'mutation':
gqlClassDecorator.addMutation(property, this.t)(classType);
break;
case GraphQLPropertyType.MUTATION:
return gqlClassDecorator.addMutation(property, this.t)(classType);

case 'query':
gqlClassDecorator.addQuery(property, this.t)(classType);
break;
case GraphQLPropertyType.QUERY:
return gqlClassDecorator.addQuery(property, this.t)(classType);

case 'resolveField':
gqlClassDecorator.addResolveField(property, this.t)(classType);
break;
case GraphQLPropertyType.RESOLVE_FIELD:
return gqlClassDecorator.addResolveField(property, this.t)(classType);

case 'subscription':
gqlClassDecorator.addSubscription(property, this.t)(classType);
break;
case GraphQLPropertyType.RESOLVE_REFERENCE:
return gqlClassDecorator.addResolveReference(
property,
this.t,
)(classType);

case GraphQLPropertyType.SUBSCRIPTION:
return gqlClassDecorator.addSubscription(property, this.t)(classType);

default:
throw new Error('Invalid type');
Expand All @@ -175,7 +196,7 @@ class GraphQLPropertyDecorator {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = 'query';
this.t.type = GraphQLPropertyType.QUERY;
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;
}
Expand All @@ -184,7 +205,7 @@ class GraphQLPropertyDecorator {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = 'mutation';
this.t.type = GraphQLPropertyType.MUTATION;
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;
}
Expand All @@ -193,17 +214,43 @@ class GraphQLPropertyDecorator {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = 'subscription';
this.t.type = GraphQLPropertyType.SUBSCRIPTION;
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;
}

resolveReference(options?: GraphQLResolveReferencedOptions) {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = GraphQLPropertyType.RESOLVE_REFERENCE;
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;

this.t.checks.add(resolverDecorator => {
if (!resolverDecorator.t.type) {
throw new Error(
'Can only resolve references for resolvers with a type @graphql.resolver<T>()',
);
}

const resolverType = reflect(this.t.classType);
const reflectionClass = ReflectionClass.from(resolverType);

if (!reflectionClass.hasMethod(this.t.name)) {
const typeName = requireTypeName(resolverDecorator.t.type);
throw new Error(
`No field ${this.t.name} found on type ${typeName} for reference resolver method ${this.t.property} on resolver ${this.t.classType.name}`,
);
}
});
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
resolveField(options?: GraphQLResolveFieldOptions) {
if (options?.name) {
this.t.name = options.name;
}
this.t.type = 'resolveField';
this.t.type = GraphQLPropertyType.RESOLVE_FIELD;
this.t.description = options?.description;
this.t.deprecationReason = options?.deprecationReason;

Expand Down
75 changes: 57 additions & 18 deletions packages/core/src/lib/directives.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,66 @@
import {
GraphQLDirective,
GraphQLSkipDirective,
GraphQLIncludeDirective,
} from 'graphql';
import {
GraphQLDeferDirective,
GraphQLStreamDirective,
} from '@graphql-tools/utils';
isSameType,
metaAnnotation,
ReflectionKind,
Type,
TypeAnnotation,
TypeLiteral,
TypeObjectLiteral,
typeOf,
} from '@deepkit/type';
// import {
// GraphQLDirective,
// GraphQLSkipDirective,
// GraphQLIncludeDirective,
// } from 'graphql';
// import {
// GraphQLDeferDirective,
// GraphQLStreamDirective,
// } from '@graphql-tools/utils';

export const DIRECTIVE_META_NAME = 'directive';

export type Directive<
D extends GraphQLDirective | string,
A extends any[] = [],
> = { __meta?: [typeof DIRECTIVE_META_NAME, D, A] };
export const FEDERATION_KEY_DIRECTIVE_NAME = 'key';

export type Defer = Directive<typeof GraphQLDeferDirective>;
// export type Directive<
// D extends GraphQLDirective | string,
// A extends any[] = [],
// > = { __meta?: [typeof DIRECTIVE_META_NAME, D, A] };
//
// export type Defer = Directive<typeof GraphQLDeferDirective>;
//
// export type Stream = Directive<typeof GraphQLStreamDirective>;
//
// export type Skip = Directive<typeof GraphQLSkipDirective>;
//
// export type Include = Directive<typeof GraphQLIncludeDirective>;

export type Stream = Directive<typeof GraphQLStreamDirective>;
// TODO: use jsdoc @deprecated instead
// export type Deprecated = Directive<'deprecated', { reason: string }>;

export type Skip = Directive<typeof GraphQLSkipDirective>;
export interface FieldDirectiveOptions<Name extends string> {
readonly name: Name;
}

export type Include = Directive<typeof GraphQLIncludeDirective>;
export type FieldDirective<Name extends string> = TypeAnnotation<
typeof DIRECTIVE_META_NAME,
FieldDirectiveOptions<Name>
>;

// TODO: use jsdoc @deprecated instead
// export type Deprecated = Directive<'deprecated', { reason: string }>;
export type FederationKey = FieldDirective<
typeof FEDERATION_KEY_DIRECTIVE_NAME
>;

export function getMetaAnnotationDirectives(type: Type): Type[] | undefined {
return metaAnnotation.getForName(type, DIRECTIVE_META_NAME);
}

export function hasMetaAnnotationDirective(
annotations: readonly Type[],
name: string,
): boolean {
return annotations.some(
annotation =>
(annotation.typeArguments![0] as TypeLiteral).literal === name,
);
}
Loading

0 comments on commit 73fd553

Please sign in to comment.