diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1d6b6cf..88ba201 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,10 +18,6 @@ concurrency: group: pages cancel-in-progress: false -defaults: - run: - working-directory: docs - jobs: build: runs-on: ubuntu-latest @@ -35,8 +31,12 @@ jobs: - name: Install Dependencies run: bun install + - name: Build Plugin + run: bun run build + - name: Build Docs run: bun docs:build + working-directory: docs - name: Setup Pages uses: actions/configure-pages@v5 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..7ec769b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +*.hbs diff --git a/docs/.gitignore b/docs/.gitignore index b2d6de3..c2240b7 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -7,6 +7,7 @@ # Generated files .docusaurus .cache-loader +docs/configuration.md # Misc .DS_Store diff --git a/docs/bun.lockb b/docs/bun.lockb index 898141a..883a6ed 100755 Binary files a/docs/bun.lockb and b/docs/bun.lockb differ diff --git a/docs/docs/class-consolidation.md b/docs/docs/class-consolidation.md new file mode 100644 index 0000000..fcbfe7c --- /dev/null +++ b/docs/docs/class-consolidation.md @@ -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. diff --git a/docs/docs/configuration.md b/docs/docs/configuration.md deleted file mode 100644 index 052ebe4..0000000 --- a/docs/docs/configuration.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -sidebar_position: 3 ---- - -# Configuration - -```ts reference title="Config Schema" -https://github.com/ExpediaGroup/graphql-kotlin-codegen/blob/main/src/config.ts#L1-L100000 -``` diff --git a/docs/docs/recommended-usage.md b/docs/docs/recommended-usage.md index 2f7ad56..5891c0e 100644 --- a/docs/docs/recommended-usage.md +++ b/docs/docs/recommended-usage.md @@ -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. diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 49a23a0..a2b23e8 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -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", @@ -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", @@ -76,6 +66,4 @@ const config: Config = { darkTheme: prismThemes.dracula, }, } satisfies Preset.ThemeConfig, -}; - -export default config; +} satisfies Config; diff --git a/docs/jsdoc.conf b/docs/jsdoc.conf new file mode 100644 index 0000000..d431ba4 --- /dev/null +++ b/docs/jsdoc.conf @@ -0,0 +1,5 @@ +{ + "source": { + "includePattern": ".+\\.(js|cjs|mjs)$" + } +} diff --git a/docs/package.json b/docs/package.json index be52a05..f659ba1 100644 --- a/docs/package.json +++ b/docs/package.json @@ -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", @@ -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", diff --git a/docs/partials/main.hbs b/docs/partials/main.hbs new file mode 100644 index 0000000..cfa0b8a --- /dev/null +++ b/docs/partials/main.hbs @@ -0,0 +1,7 @@ +--- +sidebar_position: 3 +--- + +# Configuration + +{{>all-docs~}} diff --git a/docs/partials/scope.hbs b/docs/partials/scope.hbs new file mode 100644 index 0000000..e69de29 diff --git a/src/config.ts b/src/config.ts index 7bfa079..f854d17 100644 --- a/src/config.ts +++ b/src/config.ts @@ -40,12 +40,15 @@ export const configSchema = object({ 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 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())), /** @@ -67,44 +70,40 @@ export const configSchema = object({ ), /** * Denotes Kotlin annotations to replace GraphQL directives. - * @example [{ directive: "myDirective", kotlinAnnotations: ['@MyAnnotation("some argument")'] }] + * + * `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({ - /** - * 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)), }), ), ), /** * 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 * [ @@ -120,6 +119,7 @@ export const configSchema = object({ * classMethods: "COMPLETABLE_FUTURE", * } * ] + * @link https://opensource.expediagroup.com/graphql-kotlin-codegen/docs/recommended-usage */ resolverClasses: optional( array( @@ -132,8 +132,9 @@ 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`. Defaults to `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(