Skip to content

Commit

Permalink
Docs and small fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
captbaritone committed Jul 18, 2024
1 parent 7843873 commit e79ff86
Show file tree
Hide file tree
Showing 13 changed files with 100 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function Foo() {}
-----------------
OUTPUT
-----------------
src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts:1:5 - error: `@gqlInterface` can only be used on interface declarations. e.g. `interface MyInterface {}`
src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts:1:5 - error: `@gqlInterface` can only be used on interface or abstract class declarations. e.g. `interface MyInterface {}` or `abstract class MyInterface {}`

1 /** @gqlInterface Person */
~~~~~~~~~~~~~~~~~~~~~
10 changes: 7 additions & 3 deletions website/docs/04-docblock-tags/01-types.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ GraphQL types can be defined by placing a `@gqlType` docblock directly before a:
- Interface declaration
- Type alias of a literal type or `unknown`

If model your GraphQL resolvers using classes, simply add a `@gqlType` docblock
To model your GraphQL resolvers using classes, simply add a `@gqlType` docblock
before the class containing that type's resolvers.

<GratsCode out={TypesClass} mode="ts" />
Expand All @@ -26,6 +26,10 @@ an interface.

<GratsCode out={TypesInterface} mode="ts" />

:::info
Types declared using TypeScript classes or interfaces will inherit GraphQL fields and interfaces from all classes/interfaces they extend/implement.
:::

Finally, if your types are represented in your code by named types, simply add a
`@gqlType` docblock before the type alias which describes that type.

Expand Down Expand Up @@ -73,7 +77,7 @@ Will generate the following GraphQL schema:

<GratsCode out={TypesInterfaceImplementInterface} mode="gql" />

#### Type Alias
### Type Alias

Types declared using a type alias _may not_ implement a GraphQL interface. Instead, we recommend using a TypeScript interface to model your GraphQL type.

Expand All @@ -84,5 +88,5 @@ See [Interfaces](./05-interfaces.mdx) for more information about defining interf
:::

:::note
Grats must be able to determine the typename of any type which implements an interface. To achieve this Grats will validate that all implementors of an interface either define a `__typename: "MyType" as const` property or are exported classes. Grats can use either to determin the typename at runtime.
Grats must be able to determine the typename of any type which implements an interface. To achieve this Grats will validate that all implementors of an interface either define a `__typename: "MyType" as const` property or are exported classes. Grats can use either to determine the typename at runtime.
:::
22 changes: 15 additions & 7 deletions website/docs/04-docblock-tags/05-interfaces.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import GratsCode from "@site/src/components/GratsCode";
import InterfaceDeclaration from "!!raw-loader!./snippets/04-interface-declaration.out";
import InterfaceClassDeclaration from "!!raw-loader!./snippets/04-interface-class-declaration.out";
import NodeInterface from "!!raw-loader!./snippets/04-merged-interface-renaming.out";
import InterfaceImplementingInterface from "!!raw-loader!./snippets/04-interface-implement-interface.out";
import InterfaceFieldCommonImpl from "!!raw-loader!./snippets/04-interface-field-common-impl.out";
Expand All @@ -9,12 +10,21 @@ import InterfaceFieldCommonImpl from "!!raw-loader!./snippets/04-interface-field
GraphQL interfaces can be defined by placing a `@gqlInterface` docblock directly before an:

- Interface declaration
- Class declaration

<GratsCode out={InterfaceDeclaration} mode="ts" />
To model your GraphQL interface using classes, simply add a `@gqlInterface` docblock
before the class. Note that with this approach, implementors of the interface will also inherit the field methods.

<GratsCode out={InterfaceClassDeclaration} mode="ts" />

## Shared Field Implementation
If your types are represented in your code by TypeScript interfaces, simply add a
`@gqlType` docblock before the interface representing that type. Note that by
using `@gqlType` on an interface, Grats will treat it as a GraphQL type and not
an interface.

If you wish to define field which has a single implementation that is shared by all implementors, you can use the [function style of `@gqlField`](./02-fields.mdx#functional-style-fields) to define the field. This will automatically add the field to all implementors of the interface.
<GratsCode out={InterfaceDeclaration} mode="ts" />

If you wish to provide shared implementations for fields in an interface defined in this way, you can use the [functional style fields](./02-fields.mdx#functional-style-fields).

<GratsCode out={InterfaceFieldCommonImpl} mode="both" />

Expand All @@ -32,10 +42,8 @@ Which will generate the following GraphQL schema:

<GratsCode out={InterfaceImplementingInterface} mode="gql" />

---

:::note
Each implementor of an interface must declare define all the fields required by the interface with `/** @gqlField */`. This means that if you have an interface that implements another interface, you must define all the fields required by both interfaces.
:::info
Interfaces will inherit GraphQL fields and interfaces from all interfaces they extend.
:::

## Merged Interfaces
Expand Down
2 changes: 1 addition & 1 deletion website/docs/04-docblock-tags/06-unions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,5 @@ All the types referenced in the TypeScript union but be explicitly annotated wit
:::

:::note
Grats must be able to determine the typename of any type which members of a union. To achieve this Grats will validate that all member types either define a `__typename: "MyType" as const` property or are exported classes. Grats can use either to determin the typename at runtime.
Grats must be able to determine the typename of any type which members of a union. To achieve this Grats will validate that all member types either define a `__typename: "MyType" as const` property or are exported classes. Grats can use either to determine the typename at runtime.
:::
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @gqlInterface */
class MyInterfaceClass {
/** @gqlField */
someField(): string {
return "someField";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/** @gqlInterface */
class MyInterfaceClass {
/** @gqlField */
someField(): string {
return "someField";
}
}

=== SNIP ===
interface MyInterfaceClass {
someField: String
}
=== SNIP ===
import { GraphQLSchema, GraphQLInterfaceType, GraphQLString } from "graphql";
export function getSchema(): GraphQLSchema {
const MyInterfaceClassType: GraphQLInterfaceType = new GraphQLInterfaceType({
name: "MyInterfaceClass",
fields() {
return {
someField: {
name: "someField",
type: GraphQLString
}
};
}
});
return new GraphQLSchema({
types: [MyInterfaceClassType]
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* A description of my interface.
* @gqlInterface MyInterfaceName
*/
interface MyClass {
interface MyInterface {
/** @gqlField */
someField: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* A description of my interface.
* @gqlInterface MyInterfaceName
*/
interface MyClass {
interface MyInterface {
/** @gqlField */
someField: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ interface Person {
// highlight-start
interface User extends Person {
// highlight-end
/** @gqlField */
name: string;

/** @gqlField */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ interface Person {
// highlight-start
interface User extends Person {
// highlight-end
/** @gqlField */
name: string;

/** @gqlField */
Expand Down
35 changes: 35 additions & 0 deletions website/docs/05-guides/09-inheritance.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Inheritance

Because Grats marries the worlds of TypeScript and GraphQL ends up implementing a version of inheritance that is something of a hybrid between the two. This guide explains the principles of inheritance in Grats and shows practical examples of how it works in practice.

Lets start with a reminder of how inheritnace works in TypeScript and GraphQL respectively and then we'll see how Grats combines the two.

## TypeScript Inheritance

TypeScript implemenets single inheritance, meaning that a class can only inherit from at most one other class. This is done using the `extends` keyword. When a class extends another class, it inherits all the fields and methods of the parent class. Note that this works recursively.

Classes in TypeScript may also implement interfaces. While a class may implement multiple interfaces, the interfaces are simply type-checking contracts and do not add any fields or methods to the class.

## GraphQL Inheritance

GraphQL does not have any notion of inheritance. While GraphQL types and interfacs can implement interfaces, implementing an interface does not automatically add any fields to the type. It's simply a type-checking contract, and each type or interface must explicitly define the fields required by any interfaces it implements.

Similarly, if a type implements an iterface with itself implements a second interface, the type must explicitly declare that it also implements that second interface, or else it will trigger a validation error.

## Grats Inheritance

Grats tries to match the semantics of TypeScript as much as possible while still generating a valid GraphQL schema.

If a `@gqlType` class extends another class, Grats will automatically infer that any fields marked as `@gqlField` in the parent class should also be exposed in the child class.

Similarly, if a `@gqlType` class implements interfaces, Grats will automatically infer that any fields marked as `@gqlField` in any of those interface should also be exposed on the class.

Both of these are done recursively, so if a parent class implements an interface which extends another interface, the child class will inherit all the fields from both interfaces.

### Overrides/Precedence

In terms of runtime behavior, the code being executed is TypeScript and thus will match TypeScript's semantics. Methods on a child class will override methods on a parent class.

If a field or method is marked as a `@gqlField` on both a parent class and a child class, the child class will take precedence. This can be useful if you want the child class to have a different GraphQL declaration from its parent. For example, you might want to add a different description, or one might be deprecated while the other is not, or the child might have a return type which is a subtype of the parent's return type.

### Function style fields
1 change: 1 addition & 0 deletions website/src/components/PlaygroundFeatures/defaultState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const DEFAULT_STATE: State = {
config: {
nullableByDefault: true,
reportTypeScriptTypeErrors: true,
importModuleSpecifierEnding: "",
},
view: {
outputOption: "sdl",
Expand Down
1 change: 1 addition & 0 deletions website/src/components/PlaygroundFeatures/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type State = {
config: {
nullableByDefault: boolean;
reportTypeScriptTypeErrors: boolean;
importModuleSpecifierEnding: string;
};
view: {
showGratsDirectives: boolean;
Expand Down

0 comments on commit e79ff86

Please sign in to comment.