diff --git a/src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts.expected b/src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts.expected
index dcb94a3c..9cb751d6 100644
--- a/src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts.expected
+++ b/src/tests/fixtures/interfaces/TagAttachedToWrongNode.ts.expected
@@ -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 */
~~~~~~~~~~~~~~~~~~~~~
diff --git a/website/docs/04-docblock-tags/01-types.mdx b/website/docs/04-docblock-tags/01-types.mdx
index aed688d9..914336cd 100644
--- a/website/docs/04-docblock-tags/01-types.mdx
+++ b/website/docs/04-docblock-tags/01-types.mdx
@@ -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.
@@ -26,6 +26,10 @@ an interface.
+:::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.
@@ -73,7 +77,7 @@ Will generate the following GraphQL schema:
-#### 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.
@@ -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.
:::
diff --git a/website/docs/04-docblock-tags/05-interfaces.mdx b/website/docs/04-docblock-tags/05-interfaces.mdx
index e1f8efe1..ea56af23 100644
--- a/website/docs/04-docblock-tags/05-interfaces.mdx
+++ b/website/docs/04-docblock-tags/05-interfaces.mdx
@@ -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";
@@ -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
-
+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.
+
+
-## 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.
+
+
+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).
@@ -32,10 +42,8 @@ Which will generate the following GraphQL schema:
----
-
-:::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
diff --git a/website/docs/04-docblock-tags/06-unions.mdx b/website/docs/04-docblock-tags/06-unions.mdx
index eee510e9..93db72eb 100644
--- a/website/docs/04-docblock-tags/06-unions.mdx
+++ b/website/docs/04-docblock-tags/06-unions.mdx
@@ -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.
:::
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.grats.ts b/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.grats.ts
new file mode 100644
index 00000000..1a3b6d91
--- /dev/null
+++ b/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.grats.ts
@@ -0,0 +1,7 @@
+/** @gqlInterface */
+class MyInterfaceClass {
+ /** @gqlField */
+ someField(): string {
+ return "someField";
+ }
+}
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.out b/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.out
new file mode 100644
index 00000000..ba6b9a99
--- /dev/null
+++ b/website/docs/04-docblock-tags/snippets/04-interface-class-declaration.out
@@ -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]
+ });
+}
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-declaration.grats.ts b/website/docs/04-docblock-tags/snippets/04-interface-declaration.grats.ts
index 00fa0b15..20b0fb56 100644
--- a/website/docs/04-docblock-tags/snippets/04-interface-declaration.grats.ts
+++ b/website/docs/04-docblock-tags/snippets/04-interface-declaration.grats.ts
@@ -2,7 +2,7 @@
* A description of my interface.
* @gqlInterface MyInterfaceName
*/
-interface MyClass {
+interface MyInterface {
/** @gqlField */
someField: string;
}
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-declaration.out b/website/docs/04-docblock-tags/snippets/04-interface-declaration.out
index 97db0c08..2a3c4adf 100644
--- a/website/docs/04-docblock-tags/snippets/04-interface-declaration.out
+++ b/website/docs/04-docblock-tags/snippets/04-interface-declaration.out
@@ -2,7 +2,7 @@
* A description of my interface.
* @gqlInterface MyInterfaceName
*/
-interface MyClass {
+interface MyInterface {
/** @gqlField */
someField: string;
}
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.grats.ts b/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.grats.ts
index 6add05a9..5da5ceb5 100644
--- a/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.grats.ts
+++ b/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.grats.ts
@@ -8,7 +8,6 @@ interface Person {
// highlight-start
interface User extends Person {
// highlight-end
- /** @gqlField */
name: string;
/** @gqlField */
diff --git a/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.out b/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.out
index 1991858e..55e6fe69 100644
--- a/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.out
+++ b/website/docs/04-docblock-tags/snippets/04-interface-implement-interface.out
@@ -8,7 +8,6 @@ interface Person {
// highlight-start
interface User extends Person {
// highlight-end
- /** @gqlField */
name: string;
/** @gqlField */
diff --git a/website/docs/05-guides/09-inheritance.mdx b/website/docs/05-guides/09-inheritance.mdx
new file mode 100644
index 00000000..f20df078
--- /dev/null
+++ b/website/docs/05-guides/09-inheritance.mdx
@@ -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
diff --git a/website/src/components/PlaygroundFeatures/defaultState.ts b/website/src/components/PlaygroundFeatures/defaultState.ts
index c0fde9c0..de98512e 100644
--- a/website/src/components/PlaygroundFeatures/defaultState.ts
+++ b/website/src/components/PlaygroundFeatures/defaultState.ts
@@ -37,6 +37,7 @@ export const DEFAULT_STATE: State = {
config: {
nullableByDefault: true,
reportTypeScriptTypeErrors: true,
+ importModuleSpecifierEnding: "",
},
view: {
outputOption: "sdl",
diff --git a/website/src/components/PlaygroundFeatures/store.ts b/website/src/components/PlaygroundFeatures/store.ts
index bebf36bf..16aa757e 100644
--- a/website/src/components/PlaygroundFeatures/store.ts
+++ b/website/src/components/PlaygroundFeatures/store.ts
@@ -10,6 +10,7 @@ export type State = {
config: {
nullableByDefault: boolean;
reportTypeScriptTypeErrors: boolean;
+ importModuleSpecifierEnding: string;
};
view: {
showGratsDirectives: boolean;