Skip to content

Commit 135a5e2

Browse files
committed
docs: improve docs and auto generate config docs (#56)
* docs: document class consolidation * auto generate configuration docs
1 parent 843e2ee commit 135a5e2

13 files changed

+176
-84
lines changed

.github/workflows/docs.yml

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ concurrency:
1818
group: pages
1919
cancel-in-progress: false
2020

21-
defaults:
22-
run:
23-
working-directory: docs
24-
2521
jobs:
2622
build:
2723
runs-on: ubuntu-latest
@@ -35,8 +31,16 @@ jobs:
3531
- name: Install Dependencies
3632
run: bun install
3733

34+
- name: Build Plugin
35+
run: bun run build
36+
37+
- name: Install Docs Dependencies
38+
run: bun install
39+
working-directory: docs
40+
3841
- name: Build Docs
3942
run: bun docs:build
43+
working-directory: docs
4044

4145
- name: Setup Pages
4246
uses: actions/configure-pages@v5

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.hbs

docs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
# Generated files
88
.docusaurus
99
.cache-loader
10+
docs/configuration.md
1011

1112
# Misc
1213
.DS_Store

docs/bun.lockb

22.5 KB
Binary file not shown.

docs/docs/class-consolidation.md

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
sidebar_position: 5
3+
---
4+
5+
# Class Consolidation
6+
7+
In GraphQL, it's common to have input types that mirror output types. For example, you might have a `UserInput` type for creating a user and a `User` type for querying a user. These types might have the same fields but are treated as separate types in GraphQL.
8+
9+
With the class consolidation feature, GraphQL Kotlin Codegen can detect when these types are equivalent and consolidate them into a single Kotlin class.
10+
If this class functions in your resolver code as both an input and an output type, GraphQL Kotlin will subsequently
11+
transform it into the separate input and output types we started with.
12+
13+
## How It Works
14+
15+
The class consolidation feature works by comparing the fields of input and output types. If the fields and their types
16+
are exactly the same, the types are considered equivalent.
17+
18+
Here's an example:
19+
20+
```graphql
21+
input UserInput {
22+
name: String!
23+
email: String!
24+
}
25+
26+
type User {
27+
name: String!
28+
email: String!
29+
}
30+
```
31+
32+
In this case, `UserInput` and `User` have the same fields, so they would be consolidated into a single Kotlin class:
33+
34+
```kotlin
35+
data class User(
36+
val name: String,
37+
val email: String
38+
)
39+
```
40+
41+
This also works recursively. If the fields of a type are themselves input or output types, they will be consolidated as well.
42+
43+
```graphql
44+
input UserInput {
45+
name: NameInput!
46+
email: String!
47+
}
48+
49+
input NameInput {
50+
first: String!
51+
last: String!
52+
}
53+
54+
type User {
55+
name: Name!
56+
email: String!
57+
}
58+
59+
type Name {
60+
first: String!
61+
last: String!
62+
}
63+
```
64+
65+
```kotlin
66+
data class User(
67+
val name: Name,
68+
val email: String
69+
)
70+
71+
data class Name(
72+
val first: String,
73+
val last: String
74+
)
75+
```
76+
77+
## Limitations
78+
79+
The class consolidation feature only works with types that have the same fields with the same types.
80+
If the fields are different, the types will not be consolidated. Instead, individual classes will be generated with the
81+
`@GraphQLValidObjectLocations` annotation, enforcing that the class can only be used as either an input or output type.
82+
Check out the [GraphQL Kotlin docs](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/customizing-schemas/restricting-input-output)
83+
to learn more about this annotation.

docs/docs/configuration.md

Lines changed: 0 additions & 9 deletions
This file was deleted.

docs/docs/recommended-usage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class MyQuery : Query, QueryInterface() {
6060

6161
```
6262

63-
The resulting source code is at risk of being extremely unperformant. The `MyType` class is a data class, which means
63+
The resulting source code is extremely unperformant. The `MyType` class is a data class, which means
6464
that the `field1` and `field2` properties are both initialized when the `MyType` object is created, and
6565
`myExpensiveCall1()` and `myExpensiveCall2()` will both be called in sequence! Even if I only query for `field1`, not
6666
only will `myExpensiveCall2()` still run, but it will also wait until `myExpensiveCall1()` is totally finished.

docs/docusaurus.config.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { themes as prismThemes } from "prism-react-renderer";
22
import type { Config } from "@docusaurus/types";
33
import type * as Preset from "@docusaurus/preset-classic";
4-
import * as path from "path";
54

6-
const config: Config = {
5+
export default {
76
title: "GraphQL Kotlin Codegen",
87
favicon: "img/favicon.ico",
98

@@ -30,15 +29,6 @@ const config: Config = {
3029
} satisfies Preset.Options,
3130
],
3231
],
33-
themes: [
34-
path.resolve(
35-
__dirname,
36-
"node_modules",
37-
"docusaurus-theme-github-codeblock",
38-
"build",
39-
"index.js",
40-
),
41-
],
4232
themeConfig: {
4333
navbar: {
4434
title: "GraphQL Kotlin Codegen",
@@ -76,6 +66,4 @@ const config: Config = {
7666
darkTheme: prismThemes.dracula,
7767
},
7868
} satisfies Preset.ThemeConfig,
79-
};
80-
81-
export default config;
69+
} satisfies Config;

docs/jsdoc.conf

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"source": {
3+
"includePattern": ".+\\.(js|cjs|mjs)$"
4+
}
5+
}

docs/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"private": true,
44
"scripts": {
55
"docs:start": "docusaurus start",
6-
"docs:build": "tsc && docusaurus build"
6+
"docs:build": "tsc && bun generate && docusaurus build",
7+
"generate": "jsdoc2md --files ../dist/plugin.cjs --partial partials/main.hbs --partial partials/scope.hbs -c jsdoc.conf > docs/configuration.md"
78
},
89
"devDependencies": {
910
"@docusaurus/core": "3.2.1",
@@ -13,7 +14,7 @@
1314
"@docusaurus/types": "3.2.1",
1415
"@mdx-js/react": "3.0.1",
1516
"clsx": "2.1.1",
16-
"docusaurus-theme-github-codeblock": "2.0.2",
17+
"jsdoc-to-markdown": "8.0.1",
1718
"prism-react-renderer": "2.3.1",
1819
"react": "18.3.1",
1920
"react-dom": "18.3.1",

docs/partials/main.hbs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
sidebar_position: 3
3+
---
4+
5+
# Configuration
6+
7+
{{>all-docs~}}

docs/partials/scope.hbs

Whitespace-only changes.

src/config.ts

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -24,28 +24,51 @@ import {
2424
import { Kind } from "graphql";
2525

2626
export const configSchema = object({
27-
/**
28-
* The package name for the generated file. Defaults to the directory containing the generated file.
29-
* @example "com.example.generated"
30-
*/
31-
packageName: optional(string()),
32-
/**
33-
* Only generate types matching those in this list. If empty, no types will be generated. If omitted, all types will be generated.
34-
* @example ["MyType", "MyEnum"]
35-
*/
36-
onlyTypes: optional(array(string())),
37-
/**
38-
* Determines whether to generate dependent types from types listed in `onlyTypes`. Defaults to `true`.
39-
*/
40-
includeDependentTypes: optional(boolean()),
4127
/**
4228
* Limits dependent types to include from `onlyTypes` list. Can be used to exclude classes that are imported from external packages.
43-
* @description If `MyType` depends on `MyDependentType1` and `MyDependentType2`, we can allow `MyDependentType2` to be imported externally by including its import in `extraImports` and omitting it in the `dependentTypesInScope` list: `["MyType", "MyDependentType1"]`
29+
*
30+
* If `MyType` depends on `MyDependentType1` and `MyDependentType2`, we can allow `MyDependentType2` to be imported
31+
* externally by including its import in `extraImports` and omitting it in the `dependentTypesInScope` list.
32+
* @example ["MyType", "MyDependentType1"]
4433
*/
4534
dependentTypesInScope: optional(array(string())),
35+
/**
36+
* Denotes Kotlin annotations to replace GraphQL directives.
37+
*
38+
* `directive` is the name of the directive to replace, and `kotlinAnnotations` is a list of Kotlin annotations to replace the directive with.
39+
*
40+
* Use `argumentsToRetain` to forward arguments from the directive directly to the Kotlin annotation. Can be INT, FLOAT, STRING, BOOLEAN, or ENUM. ```@YourGraphQLDirective(arg1: "value1") -> @YourKotlinAnnotation(arg1 = "value1")```
41+
*
42+
* Use `definitionType` to apply the directive replacement to a specific kind of type definition. If omitted, the replacement will apply to all definition types.
43+
*
44+
* @example
45+
* [{ directive: "myDirective", kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
46+
*
47+
* @example
48+
* [{ directive: "myDirective", definitionType: Kind.INPUT_OBJECT_TYPE_DEFINITION, kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
49+
*/
50+
directiveReplacements: optional(
51+
array(
52+
object({
53+
directive: string(),
54+
kotlinAnnotations: array(
55+
union([
56+
string(),
57+
object({
58+
annotationName: string(),
59+
argumentsToRetain: array(string()),
60+
}),
61+
]),
62+
),
63+
definitionType: optional(enum_(Kind)),
64+
}),
65+
),
66+
),
4667
/**
4768
* Denotes Kotlin classes representing union types to be treated as interfaces rather than annotation classes.
48-
* @description This should be used for types outside `dependentTypesInScope` that are not generated by the plugin. Only use when unionGeneration is set to `ANNOTATION_CLASS`.
69+
*
70+
* This should be used for types outside `dependentTypesInScope` that are not generated by the plugin.
71+
* Only use when unionGeneration is set to `ANNOTATION_CLASS`.
4972
*/
5073
externalUnionsAsInterfaces: optional(array(string())),
5174
/**
@@ -66,45 +89,28 @@ export const configSchema = object({
6689
),
6790
),
6891
/**
69-
* Denotes Kotlin annotations to replace GraphQL directives.
70-
* @example [{ directive: "myDirective", kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
92+
* Determines whether to generate dependent types from types listed in `onlyTypes`.
93+
*
94+
* @default true
7195
*/
72-
directiveReplacements: optional(
73-
array(
74-
object({
75-
/**
76-
* The name of the directive to replace.
77-
*/
78-
directive: string(),
79-
/**
80-
* A list of Kotlin annotations to replace the directive with.
81-
*/
82-
kotlinAnnotations: array(
83-
union([
84-
string(),
85-
object({
86-
/**
87-
* The name of the annotation to replace the directive with.
88-
*/
89-
annotationName: string(),
90-
/**
91-
* The arguments to forward from the directive directly to the Kotlin annotation. Can be INT, FLOAT, STRING, BOOLEAN, or ENUM.
92-
* @example @YourGraphQLDirective(arg1: "value1") -> @YourKotlinAnnotation(arg1 = "value1")
93-
*/
94-
argumentsToRetain: array(string()),
95-
}),
96-
]),
97-
),
98-
/**
99-
* The type definition to apply the directive replacement to. If omitted, the replacement will apply to all definition types.
100-
*/
101-
definitionType: optional(enum_(Kind)),
102-
}),
103-
),
104-
),
96+
includeDependentTypes: optional(boolean()),
97+
/**
98+
* Only generate types matching those in this list. If empty, no types will be generated. If omitted, all types will be generated.
99+
* @example ["MyType", "MyEnum"]
100+
*/
101+
onlyTypes: optional(array(string())),
102+
/**
103+
* The package name for the generated file.
104+
*
105+
* @default path.to.directory.containing.generated.file
106+
*
107+
* @example "com.example.generated"
108+
*/
109+
packageName: optional(string()),
105110
/**
106111
* Denotes types that should be generated as classes. Resolver classes can inherit from these to enforce a type contract.
107-
* @description Type names can be optionally passed with the classMethods config to generate suspend functions or
112+
*
113+
* Type names can be optionally passed with the classMethods config to generate `suspend` functions or
108114
* `java.util.concurrent.CompletableFuture` functions.
109115
* @example
110116
* [
@@ -120,6 +126,7 @@ export const configSchema = object({
120126
* classMethods: "COMPLETABLE_FUTURE",
121127
* }
122128
* ]
129+
* @link https://opensource.expediagroup.com/graphql-kotlin-codegen/docs/recommended-usage
123130
*/
124131
resolverClasses: optional(
125132
array(
@@ -132,8 +139,12 @@ export const configSchema = object({
132139
),
133140
),
134141
/**
135-
* Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`.
136-
* @description The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
142+
* Denotes the generation strategy for union types. Can be `ANNOTATION_CLASS` or `MARKER_INTERFACE`.
143+
*
144+
*
145+
* @default MARKER_INTERFACE
146+
*
147+
* The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
137148
* @link https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/
138149
*/
139150
unionGeneration: optional(

0 commit comments

Comments
 (0)