Skip to content

Commit

Permalink
docs: improve docs and auto generate config docs (#56)
Browse files Browse the repository at this point in the history
* docs: document class consolidation

* auto generate configuration docs
  • Loading branch information
danadajian committed May 1, 2024
1 parent 843e2ee commit 135a5e2
Show file tree
Hide file tree
Showing 13 changed files with 176 additions and 84 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ concurrency:
group: pages
cancel-in-progress: false

defaults:
run:
working-directory: docs

jobs:
build:
runs-on: ubuntu-latest
Expand All @@ -35,8 +31,16 @@ jobs:
- name: Install Dependencies
run: bun install

- name: Build Plugin
run: bun run build

- name: Install Docs Dependencies
run: bun install
working-directory: docs

- name: Build Docs
run: bun docs:build
working-directory: docs

- name: Setup Pages
uses: actions/configure-pages@v5
Expand Down
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.hbs
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Generated files
.docusaurus
.cache-loader
docs/configuration.md

# Misc
.DS_Store
Expand Down
Binary file modified docs/bun.lockb
Binary file not shown.
83 changes: 83 additions & 0 deletions docs/docs/class-consolidation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
sidebar_position: 5
---

# Class Consolidation

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.

With the class consolidation feature, GraphQL Kotlin Codegen can detect when these types are equivalent and consolidate them into a single Kotlin class.
If this class functions in your resolver code as both an input and an output type, GraphQL Kotlin will subsequently
transform it into the separate input and output types we started with.

## How It Works

The class consolidation feature works by comparing the fields of input and output types. If the fields and their types
are exactly the same, the types are considered equivalent.

Here's an example:

```graphql
input UserInput {
name: String!
email: String!
}

type User {
name: String!
email: String!
}
```

In this case, `UserInput` and `User` have the same fields, so they would be consolidated into a single Kotlin class:

```kotlin
data class User(
val name: String,
val email: String
)
```

This also works recursively. If the fields of a type are themselves input or output types, they will be consolidated as well.

```graphql
input UserInput {
name: NameInput!
email: String!
}

input NameInput {
first: String!
last: String!
}

type User {
name: Name!
email: String!
}

type Name {
first: String!
last: String!
}
```

```kotlin
data class User(
val name: Name,
val email: String
)

data class Name(
val first: String,
val last: String
)
```

## Limitations

The class consolidation feature only works with types that have the same fields with the same types.
If the fields are different, the types will not be consolidated. Instead, individual classes will be generated with the
`@GraphQLValidObjectLocations` annotation, enforcing that the class can only be used as either an input or output type.
Check out the [GraphQL Kotlin docs](https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/customizing-schemas/restricting-input-output)
to learn more about this annotation.
9 changes: 0 additions & 9 deletions docs/docs/configuration.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/docs/recommended-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class MyQuery : Query, QueryInterface() {

```

The resulting source code is at risk of being extremely unperformant. The `MyType` class is a data class, which means
The resulting source code is extremely unperformant. The `MyType` class is a data class, which means
that the `field1` and `field2` properties are both initialized when the `MyType` object is created, and
`myExpensiveCall1()` and `myExpensiveCall2()` will both be called in sequence! Even if I only query for `field1`, not
only will `myExpensiveCall2()` still run, but it will also wait until `myExpensiveCall1()` is totally finished.
Expand Down
16 changes: 2 additions & 14 deletions docs/docusaurus.config.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { themes as prismThemes } from "prism-react-renderer";
import type { Config } from "@docusaurus/types";
import type * as Preset from "@docusaurus/preset-classic";
import * as path from "path";

const config: Config = {
export default {
title: "GraphQL Kotlin Codegen",
favicon: "img/favicon.ico",

Expand All @@ -30,15 +29,6 @@ const config: Config = {
} satisfies Preset.Options,
],
],
themes: [
path.resolve(
__dirname,
"node_modules",
"docusaurus-theme-github-codeblock",
"build",
"index.js",
),
],
themeConfig: {
navbar: {
title: "GraphQL Kotlin Codegen",
Expand Down Expand Up @@ -76,6 +66,4 @@ const config: Config = {
darkTheme: prismThemes.dracula,
},
} satisfies Preset.ThemeConfig,
};

export default config;
} satisfies Config;
5 changes: 5 additions & 0 deletions docs/jsdoc.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"source": {
"includePattern": ".+\\.(js|cjs|mjs)$"
}
}
5 changes: 3 additions & 2 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"private": true,
"scripts": {
"docs:start": "docusaurus start",
"docs:build": "tsc && docusaurus build"
"docs:build": "tsc && bun generate && docusaurus build",
"generate": "jsdoc2md --files ../dist/plugin.cjs --partial partials/main.hbs --partial partials/scope.hbs -c jsdoc.conf > docs/configuration.md"
},
"devDependencies": {
"@docusaurus/core": "3.2.1",
Expand All @@ -13,7 +14,7 @@
"@docusaurus/types": "3.2.1",
"@mdx-js/react": "3.0.1",
"clsx": "2.1.1",
"docusaurus-theme-github-codeblock": "2.0.2",
"jsdoc-to-markdown": "8.0.1",
"prism-react-renderer": "2.3.1",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
7 changes: 7 additions & 0 deletions docs/partials/main.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
sidebar_position: 3
---

# Configuration

{{>all-docs~}}
Empty file added docs/partials/scope.hbs
Empty file.
119 changes: 65 additions & 54 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,51 @@ import {
import { Kind } from "graphql";

export const configSchema = object({
/**
* The package name for the generated file. Defaults to the directory containing the generated file.
* @example "com.example.generated"
*/
packageName: optional(string()),
/**
* Only generate types matching those in this list. If empty, no types will be generated. If omitted, all types will be generated.
* @example ["MyType", "MyEnum"]
*/
onlyTypes: optional(array(string())),
/**
* Determines whether to generate dependent types from types listed in `onlyTypes`. Defaults to `true`.
*/
includeDependentTypes: optional(boolean()),
/**
* Limits dependent types to include from `onlyTypes` list. Can be used to exclude classes that are imported from external packages.
* @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"]`
*
* 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.
* @example ["MyType", "MyDependentType1"]
*/
dependentTypesInScope: optional(array(string())),
/**
* Denotes Kotlin annotations to replace GraphQL directives.
*
* `directive` is the name of the directive to replace, and `kotlinAnnotations` is a list of Kotlin annotations to replace the directive with.
*
* 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")```
*
* Use `definitionType` to apply the directive replacement to a specific kind of type definition. If omitted, the replacement will apply to all definition types.
*
* @example
* [{ directive: "myDirective", kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
*
* @example
* [{ directive: "myDirective", definitionType: Kind.INPUT_OBJECT_TYPE_DEFINITION, kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
*/
directiveReplacements: optional(
array(
object({
directive: string(),
kotlinAnnotations: array(
union([
string(),
object({
annotationName: string(),
argumentsToRetain: array(string()),
}),
]),
),
definitionType: optional(enum_(Kind)),
}),
),
),
/**
* Denotes Kotlin classes representing union types to be treated as interfaces rather than annotation classes.
* @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`.
*
* This should be used for types outside `dependentTypesInScope` that are not generated by the plugin.
* Only use when unionGeneration is set to `ANNOTATION_CLASS`.
*/
externalUnionsAsInterfaces: optional(array(string())),
/**
Expand All @@ -66,45 +89,28 @@ export const configSchema = object({
),
),
/**
* Denotes Kotlin annotations to replace GraphQL directives.
* @example [{ directive: "myDirective", kotlinAnnotations: ['@MyAnnotation("some argument")'] }]
* Determines whether to generate dependent types from types listed in `onlyTypes`.
*
* @default true
*/
directiveReplacements: optional(
array(
object({
/**
* The name of the directive to replace.
*/
directive: string(),
/**
* A list of Kotlin annotations to replace the directive with.
*/
kotlinAnnotations: array(
union([
string(),
object({
/**
* The name of the annotation to replace the directive with.
*/
annotationName: string(),
/**
* The arguments to forward from the directive directly to the Kotlin annotation. Can be INT, FLOAT, STRING, BOOLEAN, or ENUM.
* @example @YourGraphQLDirective(arg1: "value1") -> @YourKotlinAnnotation(arg1 = "value1")
*/
argumentsToRetain: array(string()),
}),
]),
),
/**
* The type definition to apply the directive replacement to. If omitted, the replacement will apply to all definition types.
*/
definitionType: optional(enum_(Kind)),
}),
),
),
includeDependentTypes: optional(boolean()),
/**
* Only generate types matching those in this list. If empty, no types will be generated. If omitted, all types will be generated.
* @example ["MyType", "MyEnum"]
*/
onlyTypes: optional(array(string())),
/**
* The package name for the generated file.
*
* @default path.to.directory.containing.generated.file
*
* @example "com.example.generated"
*/
packageName: optional(string()),
/**
* Denotes types that should be generated as classes. Resolver classes can inherit from these to enforce a type contract.
* @description Type names can be optionally passed with the classMethods config to generate suspend functions or
*
* Type names can be optionally passed with the classMethods config to generate `suspend` functions or
* `java.util.concurrent.CompletableFuture` functions.
* @example
* [
Expand All @@ -120,6 +126,7 @@ export const configSchema = object({
* classMethods: "COMPLETABLE_FUTURE",
* }
* ]
* @link https://opensource.expediagroup.com/graphql-kotlin-codegen/docs/recommended-usage
*/
resolverClasses: optional(
array(
Expand All @@ -132,8 +139,12 @@ export const configSchema = object({
),
),
/**
* Denotes the generation strategy for union types. Defaults to `MARKER_INTERFACE`.
* @description The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
* Denotes the generation strategy for union types. Can be `ANNOTATION_CLASS` or `MARKER_INTERFACE`.
*
*
* @default MARKER_INTERFACE
*
* The `MARKER_INTERFACE` option is highly recommended, since it is more type-safe than using annotation classes.
* @link https://opensource.expediagroup.com/graphql-kotlin/docs/schema-generator/writing-schemas/unions/
*/
unionGeneration: optional(
Expand Down

0 comments on commit 135a5e2

Please sign in to comment.