An open API service indexing awesome lists of open source software.

https://github.com/martmists-gh/graphql-exposed

This repository holds a prototype for binding KGraphQL to Exposed's DAO models
https://github.com/martmists-gh/graphql-exposed

exposed exposed-orm graphql kotlin ksp

Last synced: 5 months ago
JSON representation

This repository holds a prototype for binding KGraphQL to Exposed's DAO models

Awesome Lists containing this project

README

          

# GraphQL-Exposed Prototype

This is a prototype project to showcase codegen for binding Exposed's DAO API to KGraphQL.
In a production environment, the gql-annotations and gql-processor folders would be external libraries.

## How it works

Let's look at an example DAO:

```kotlin
@GraphQLModel
class Film(id: EntityID) : IntEntity(id) {
companion object : IntEntityClass(FilmTable)

var name by FilmTable.name
var year by FilmTable.year
var director by Person referencedOn FilmTable.director
var characters by Character via FilmCharacterTable
var actors by Person via FilmCharacterTable
}
```

The `@GraphQLModel` annotation is processed by the gql-processor, which generates a `FilmGraphQLModel` class, remapping the properties:

```kotlin
// Automatically generated from the public properties and @GraphQLModel annotation on Film
class FilmGraphQL(internal val instance: Film) {
val id = instance.id.value
var name: kotlin.String
get() = instance.name
set(value) { instance.name = value }
var year: kotlin.Int
get() = instance.year
set(value) { instance.year = value }
var director: com.example.db.models.PersonGraphQL
get() = instance.director.graphql
set(value) { instance.director = value.instance }
var characters: Iterable
get() = instance.characters.map { com.example.db.models.CharacterGraphQL(it) }
set(value) { instance.characters = SizedCollection(value.map { it.instance }) }
var actors: Iterable
get() = instance.actors.map { com.example.db.models.PersonGraphQL(it) }
set(value) { instance.actors = SizedCollection(value.map { it.instance }) }
}
```

Then an autogenerated remapper can be used to convert DAOs to GraphQL models:

```kotlin
query("films") {
resolver { ->
Film.all().map(Film::graphql)
}
}
```

The reason for this is that the DAOs have their attributes computed lazily in a manner which is incompatible with KGraphQL's reflection-based approach.
The FilmGraphQL class instead just generates getters and setters, which KGraphQL can use to access the DAO's properties indirectly. (Except for the ID property, which is always retrieved.)

However, due to the way Exposed's DAOs work, we need to be in a transaction to access the properties. As such, we need to add the following snippet to Ktor. Note that in production, this would just be `install(GraphQLExposedPatch)`

```kotlin
install(GraphQL) {
// ...
// The Executor must NOT be set to Parallel, as this will cause ConcurrentModificationExceptions in the Transaction.
executor = Executor.DataLoaderPrepared
}

install(object : Plugin {
override val key: AttributeKey = AttributeKey("GraphQL-Database")

override fun install(pipeline: Application, configure: Unit.() -> Unit) {
pipeline.intercept(ApplicationCallPipeline.Plugins) {
if (call.request.path() == "/graphql" && call.request.httpMethod == HttpMethod.Post) {
// Start a transaction for GraphQL POST requests
newSuspendedTransaction {
proceed()
}
} else {
proceed()
}
}
}
})
```

## Hiding Fields

Fields can be hidden by using the `@HideGraphQLField` annotation:

```kotlin
@GraphQLModel
class Film(id: EntityID) : IntEntity(id) {
companion object : IntEntityClass(FilmTable)

var name by FilmTable.name
var year by FilmTable.year
var director by Person referencedOn FilmTable.director
var characters by Character via FilmCharacterTable
var actors by Person via FilmCharacterTable

@HideGraphQLField
var hiddenField by FilmTable.hiddenField
}
```

`hiddenField` will not be added to the FilmGraphQL class, and as such will not show up in the Schema.

## Limitations

Unfortunately this breaks inheritance in autogenerated models, but this can still be done by manually converting DAOs to GraphQL models.

All other features such as access rules still work, but must be defined on their GraphQL models:

```kotlin
type {
property("characters") {
// A resolver must be manually defined for accessRule to not crash
resolver { film ->
film.characters
}

accessRule { film, context ->
GraphQLError("Nobody can see the characters of a film")
}
}
}
```

## License

This project is licensed under CC0; Do whatever you want with it. However, I would appreciate it if you could credit me where applicable, in the event you were to use this as basis for a proper library and processor.