{"id":43411193,"url":"https://github.com/darkxanter/kesp","last_synced_at":"2026-02-02T16:55:32.416Z","repository":{"id":65406539,"uuid":"583132079","full_name":"darkxanter/kesp","owner":"darkxanter","description":"Kesp is a Kotlin Symbol Processor plugin for Exposed SQL DSL. It generates for you DTOs, table mappings and a CRUD repository for an Exposed table.","archived":false,"fork":false,"pushed_at":"2025-01-16T19:13:32.000Z","size":289,"stargazers_count":10,"open_issues_count":3,"forks_count":2,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-16T20:29:50.587Z","etag":null,"topics":["exposed","kotlin","kotlin-symbol-processor","ksp","orm","sql"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/darkxanter.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-28T21:33:47.000Z","updated_at":"2025-01-16T19:13:34.000Z","dependencies_parsed_at":"2024-02-18T14:27:00.296Z","dependency_job_id":"300351d6-ad9d-4d4d-8ea3-d84298777217","html_url":"https://github.com/darkxanter/kesp","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/darkxanter/kesp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkxanter%2Fkesp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkxanter%2Fkesp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkxanter%2Fkesp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkxanter%2Fkesp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/darkxanter","download_url":"https://codeload.github.com/darkxanter/kesp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/darkxanter%2Fkesp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29015652,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T16:17:30.374Z","status":"ssl_error","status_checked_at":"2026-02-02T15:58:50.469Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["exposed","kotlin","kotlin-symbol-processor","ksp","orm","sql"],"created_at":"2026-02-02T16:55:28.099Z","updated_at":"2026-02-02T16:55:32.406Z","avatar_url":"https://github.com/darkxanter.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kesp\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.github.darkxanter.exposed/kesp-annotations)](https://central.sonatype.com/artifact/io.github.darkxanter.exposed/kesp-annotations)\n\n**Kesp** is Kotlin Symbol Processor for [Exposed SQL DSL](https://github.com/JetBrains/Exposed/wiki/DSL).\nIt generates for you DTOs, table mappings and a CRUD repository for an Exposed table.\n\n\n\u003c!-- TOC --\u003e\n* [Kesp](#kesp)\n  * [Features](#features)\n  * [Annotations](#annotations)\n  * [How to use](#how-to-use)\n    * [Basic example](#basic-example)\n    * [Projection example](#projection-example)\n    * [DAO example](#dao-example)\n  * [Setup](#setup)\n* [License](#license)\n\u003c!-- TOC --\u003e\n\n## Features\n\n- generates table mappings and functions\n- generates data classes and interfaces\n- generates a CRUD repository\n- generates mappings for table projections\n- copies KDoc from columns to data class fields\n- you can use any custom columns, unlike libraries where you define a data class\n  and only a table with supported build-in columns is generated\n- generates DAOs\n\n## Annotations\n\n- `@ExposedTable` specifies that code generation will be run for the table\n- `@Projection` specifies for which table projection functions should be generated\n- `@Id` specifies the primary key of a table. Can be applied to multiple columns.\n- `@GeneratedValue` specifies that the column value is generated by a database.\n- `@ForeignKey` specifies that the column is foreign key.\n\n## How to use\n\n### Basic example\n\nOur table might look like the following:\n```kotlin\n/** User account */\nobject UserTable : LongIdTable(\"users\") {\n    /**\n     * Username\n     */\n    val username = varchar(\"username\", 255)\n\n    /** User password */\n    val password = varchar(\"password\", 255)\n\n    /** Day of birth */\n    val birthDate = date(\"birth_date\").nullable()\n\n    /** Account creation time */\n    val createdAt = timestamp(\"created_at\").defaultExpression(CurrentTimestamp())\n}\n```\n\nTo create mapping functions, DTOs and CRUD repository you simply need to add the `@ExposedTable` annotation above the target table.\nWe also need to add `@GeneratedValue` annotation above the `createdAt` column because it's generated on a database side.\n\n```kotlin\n/** User account */\n@ExposedTable\n@Projection(UserDto::class, updateFunction = true)\nobject UserTable : LongIdTable(\"users\") {\n    /**\n     * Username\n     */\n    val username = varchar(\"username\", 255)\n\n    /** User password */\n    val password = varchar(\"password\", 255)\n\n    /** Day of birth */\n    val birthDate = date(\"birth_date\").nullable()\n\n    /** Account creation time */\n    @GeneratedValue\n    val createdAt = timestamp(\"created_at\").defaultExpression(CurrentTimestamp())\n}\n```\n\nWhen we build the project we'll have:\n\n- Interfaces and data classes:\n  - `UserTableCreate` and `UserTableCreateDto` represent the row model for creating an entry in the table\n  - `UserTableFull` and `UserTableFullDto` represent the full table model used to read from the table\n\n```kotlin\n/**\n * User account\n */\npublic interface UserTableCreate {\n    /**\n     * Username\n     */\n    public val username: String\n\n    /**\n     * User password\n     */\n    public val password: String\n\n    /**\n     * Day of birth\n     */\n    public val birthDate: LocalDate?\n}\n\n/**\n * User account\n */\npublic data class UserTableCreateDto(\n    /**\n     * Username\n     */\n    public override val username: String,\n    /**\n     * User password\n     */\n    public override val password: String,\n    /**\n     * Day of birth\n     */\n    public override val birthDate: LocalDate? = null,\n) : UserTableCreate\n\n/**\n * User account\n */\npublic interface UserTableFull : UserTableCreate {\n    public val id: Long\n\n    /**\n     * Account creation time\n     */\n    public val createdAt: Instant\n}\n\n/**\n * User account\n */\npublic data class UserTableFullDto(\n    public override val id: Long,\n    /**\n     * Username\n     */\n    public override val username: String,\n    /**\n     * User password\n     */\n    public override val password: String,\n    /**\n     * Day of birth\n     */\n    public override val birthDate: LocalDate? = null,\n    /**\n     * Account creation time\n     */\n    public override val createdAt: Instant,\n) : UserTableFull\n```\n\n- \"to\" and \"from\" mapping extension functions for `ResultRow` and `UpdateBuilder`\n```kotlin\npublic fun ResultRow.toUserTableFullDto(): UserTableFullDto = UserTableFullDto(\n  id = this[UserTable.id].value,\n  username = this[UserTable.username],\n  password = this[UserTable.password],\n  birthDate = this[UserTable.birthDate],\n  createdAt = this[UserTable.createdAt],\n)\n\npublic fun ResultRow.toUserTableFullDto(alias: Alias\u003cUserTable\u003e): UserTableFullDto =\n    UserTableFullDto(\n  id = this[alias[UserTable.id]].value,\n  username = this[alias[UserTable.username]],\n  password = this[alias[UserTable.password]],\n  birthDate = this[alias[UserTable.birthDate]],\n  createdAt = this[alias[UserTable.createdAt]],\n)\n\npublic fun Iterable\u003cResultRow\u003e.toUserTableFullDtoList(): List\u003cUserTableFullDto\u003e = map {\n  it.toUserTableFullDto()\n}\n\npublic fun Iterable\u003cResultRow\u003e.toUserTableFullDtoList(alias: Alias\u003cUserTable\u003e):\n    List\u003cUserTableFullDto\u003e = map {\n  it.toUserTableFullDto(alias)\n}\n\npublic fun UpdateBuilder\u003c*\u003e.fromDto(dto: UserTableCreate): Unit {\n  this[UserTable.username] = dto.username\n  this[UserTable.password] = dto.password\n  this[UserTable.birthDate] = dto.birthDate\n}\n```\n\n- `insertDto` and `updateDto` extension functions for the table\n```kotlin\npublic fun UserTable.insertDto(dto: UserTableCreate): Long = UserTable.insertAndGetId {\n  it.fromDto(dto)\n}.value\n\npublic fun UserTable.updateDto(id: Long, dto: UserTableCreate): Int =\n    UserTable.update({UserTable.id.eq(id)}) {\n  it.fromDto(dto)\n}\n```\n\n- CRUD repository\n```kotlin\npublic open class UserTableRepository {\n  public fun find(configure: Query.() -\u003e Unit = {},\n      `where`: (SqlExpressionBuilder.() -\u003e Op\u003cBoolean\u003e)? = null): List\u003cUserTableFullDto\u003e {\n\n    return transaction {\n      if (where != null) {\n        UserTable.select(where).apply(configure).toUserTableFullDtoList()\n      } else {\n        UserTable.selectAll().apply(configure).toUserTableFullDtoList()\n      }\n    }\n  }\n\n  public fun findOne(`where`: SqlExpressionBuilder.() -\u003e Op\u003cBoolean\u003e): UserTableFullDto? {\n\n    return find(where = where).singleOrNull()\n  }\n\n  public fun findById(id: Long): UserTableFullDto? {\n\n    return findOne {\n      UserTable.id.eq(id)\n    }\n  }\n\n  public fun create(dto: UserTableCreate): Long = transaction {\n    UserTable.insertDto(dto)\n  }\n\n  public fun update(id: Long, dto: UserTableCreate): Int = transaction {\n    UserTable.updateDto(id, dto)\n  }\n\n  public fun deleteById(id: Long): Int = delete {\n    UserTable.id.eq(id)\n  }\n\n  public fun delete(`where`: UserTable.(ISqlExpressionBuilder) -\u003e Op\u003cBoolean\u003e): Int {\n\n    return transaction {\n      UserTable.deleteWhere {\n        where(it)\n      }\n    }\n  }\n}\n```\n\n### Projection example\n\nTo create mapping functions and CRUD repository for a table projection,\nyou need to add the `@Projection` annotation above the target table and point to a projection `KClass`.\n\n```kotlin\n/** User account */\n@ExposedTable\n@Projection(UserDto::class, updateFunction = true)\nobject UserTable : LongIdTable(\"users\") {\n    /**\n     * Username\n     */\n    val username = varchar(\"username\", 255)\n\n    /** User password */\n    val password = varchar(\"password\", 255)\n\n    /** Day of birth */\n    val birthDate = date(\"birth_date\").nullable()\n\n    /** Account creation time */\n    @GeneratedValue\n    val createdAt = timestamp(\"created_at\").defaultExpression(CurrentTimestamp())\n}\n\ndata class UserDto(\n    val id: Long,\n    val username: String,\n)\n```\n\nAfter build the project we'll have additional functions:\n\n- \"to\" mapping extension functions for `ResultRow`\n```kotlin\npublic fun ResultRow.toUserDto(): UserDto = UserDto(\n  id = this[UserTable.id].value,\n  username = this[UserTable.username],\n)\n\npublic fun ResultRow.toUserDto(alias: Alias\u003cUserTable\u003e): UserDto = UserDto(\n  id = this[alias[UserTable.id]].value,\n  username = this[alias[UserTable.username]],\n)\n```\n- if the `updateFunction = true` is set in the `Projection` annotation, it will be generated \"from\" mapping extension function\n```kotlin\npublic fun UpdateBuilder\u003c*\u003e.fromDto(dto: UserDto): Unit {\n  this[UserTable.username] = dto.username\n}\n```\n- and the CRUD repository will look like this\n```kotlin\npublic open class UserTableRepository {\n  public fun find(configure: Query.() -\u003e Unit = {},\n      `where`: (SqlExpressionBuilder.() -\u003e Op\u003cBoolean\u003e)? = null): List\u003cUserTableFullDto\u003e {\n\n    return transaction {\n      if (where != null) {\n        UserTable.select(where).apply(configure).toUserTableFullDtoList()\n      } else {\n        UserTable.selectAll().apply(configure).toUserTableFullDtoList()\n      }\n    }\n  }\n\n  public fun findUserDto(configure: Query.() -\u003e Unit = {},\n      `where`: (SqlExpressionBuilder.() -\u003e Op\u003cBoolean\u003e)? = null): List\u003cUserDto\u003e {\n\n    return transaction {\n      if (where != null) {\n        UserTable.slice(UserTable.id,UserTable.username).select(where).apply(configure).toUserDtoList()\n      } else {\n        UserTable.slice(UserTable.id,UserTable.username).selectAll().apply(configure).toUserDtoList()\n      }\n    }\n  }\n\n  public fun findOne(`where`: SqlExpressionBuilder.() -\u003e Op\u003cBoolean\u003e): UserTableFullDto? {\n\n    return find(where = where).singleOrNull()\n  }\n\n  public fun findById(id: Long): UserTableFullDto? {\n\n    return findOne {\n      UserTable.id.eq(id)\n    }\n  }\n\n  public fun create(dto: UserTableCreate): Long = transaction {\n    UserTable.insertDto(dto)\n  }\n\n  public fun createMultiple(dtos: Iterable\u003cUserTableCreate\u003e): Unit {\n      transaction {\n          ArticleTagsTable.batchInsertDtos(dtos)\n      }\n  }\n\n  public fun update(id: Long, dto: UserTableCreate): Int = transaction {\n    UserTable.updateDto(id, dto)\n  }\n\n  public fun updateUserDto(id: Long, dto: UserDto): Int = transaction {\n    UserTable.updateDto(id, dto)\n  }\n\n  public fun deleteById(id: Long): Int = delete {\n    UserTable.id.eq(id)\n  }\n\n  public fun delete(`where`: UserTable.(ISqlExpressionBuilder) -\u003e Op\u003cBoolean\u003e): Int {\n\n    return transaction {\n      UserTable.deleteWhere {\n        where(it)\n      }\n    }\n  }\n}\n```\n\nYou can find a complete project example in the `example` subdirectory.\n\n### DAO example\n\nTo generate a DAO for a table, you need to set the `generateDao = true` in the `@ExposedTable` annotation.\n\n**Example:**\n\n```kotlin\n@ExposedTable(generateDao = true)\nobject ArticleTable : IntIdTable() {\n    val title = text(\"title\")\n\n    @ForeignKey(UserTable::class)\n    val userId = reference(\"user_id\", UserTable)\n\n    @GeneratedValue\n    val createdAt = timestamp(\"created_at\")\n}\n\n@ExposedTable(generateDao = true)\nobject CommentTable : IntIdTable() {\n    /** Annotation [ForeignKey] is not required if column type is simple `Column\u003cInt\u003e` */\n    val articleId = integer(\"article_id\").references(ArticleTable.id)\n    val message = text(\"message\")\n\n    /** Annotation [ForeignKey] is required if column type is `Column\u003cEntity\u003c*\u003e\u003e` */\n    @ForeignKey(UserTable::class)\n    val userId = reference(\"user_id\", UserTable)\n}\n\n@ExposedTable(generateDao = true)\nobject UserTable : IntIdTable(\"users\") {\n    val username = text(\"username\")\n}\n\n```\n\n**Custom DAO:**\n\n```kotlin\nclass ArticleDao(id: EntityID\u003cInt\u003e) : ArticleTableDaoBase(id) {\n    companion object : EntityClass\u003cInt, ArticleDao\u003e(ArticleTable)\n\n    var user by UserTableDao referencedOn ArticleTable.userId\n}\n\n```\n\n**Generated code:**\n\nArticleTableDao.kt:\n```kotlin\npublic abstract class ArticleTableDaoBase(\n    id: EntityID\u003cInt\u003e,\n) : Entity\u003cInt\u003e(id) {\n    public var title: String by ArticleTable.title\n\n    public var userId: EntityID\u003cInt\u003e by ArticleTable.userId\n\n    public val createdAt: Instant by ArticleTable.createdAt\n}\n\npublic class ArticleTableDao(\n    id: EntityID\u003cInt\u003e,\n) : ArticleTableDaoBase(id) {\n    public companion object : EntityClass\u003cInt, ArticleTableDao\u003e(ArticleTable)\n}\n\npublic fun ArticleTableDaoBase.fromDto(dto: ArticleTableCreate): Unit {\n    this.title = dto.title\n    this.userId = DaoEntityID(dto.userId, UserTable)\n}\n\npublic fun ArticleTableDaoBase.toArticleTableFullDto(): ArticleTableFullDto = ArticleTableFullDto(\n    id = id.value,\n    title = title,\n    userId = userId.value,\n    createdAt = createdAt,\n)\n\npublic fun Iterable\u003cArticleTableDaoBase\u003e.toArticleTableFullDtoList(): List\u003cArticleTableFullDto\u003e = map {\n    it.toArticleTableFullDto()\n}\n\n```\n\nUserTableDao.kt:\n\n```kotlin\npublic abstract class UserTableDaoBase(\n  id: EntityID\u003cInt\u003e,\n) : Entity\u003cInt\u003e(id) {\n  public var username: String by UserTable.username\n}\n\npublic class UserTableDao(\n  id: EntityID\u003cInt\u003e,\n) : UserTableDaoBase(id) {\n  public companion object : EntityClass\u003cInt, UserTableDao\u003e(UserTable)\n}\n\npublic fun UserTableDaoBase.fromDto(dto: UserTableCreate): Unit {\n  this.username = dto.username\n}\n\npublic fun UserTableDaoBase.toUserTableFullDto(): UserTableFullDto = UserTableFullDto(\n  id = id.value,\n  username = username,\n)\n\npublic fun Iterable\u003cUserTableDaoBase\u003e.toUserTableFullDtoList(): List\u003cUserTableFullDto\u003e = map {\n  it.toUserTableFullDto()\n}\n```\n\nCommentTableDao.kt:\n\n```kotlin\npublic abstract class CommentTableDaoBase(\n  id: EntityID\u003cInt\u003e,\n) : Entity\u003cInt\u003e(id) {\n  public var articleId: Int by CommentTable.articleId\n\n  public var message: String by CommentTable.message\n\n  public var userId: EntityID\u003cInt\u003e by CommentTable.userId\n}\n\npublic class CommentTableDao(\n  id: EntityID\u003cInt\u003e,\n) : CommentTableDaoBase(id) {\n  public companion object : EntityClass\u003cInt, CommentTableDao\u003e(CommentTable)\n}\n\npublic fun CommentTableDaoBase.fromDto(dto: CommentTableCreate): Unit {\n  this.articleId = dto.articleId\n  this.message = dto.message\n  this.userId = DaoEntityID(dto.userId, UserTable)\n}\n\npublic fun CommentTableDaoBase.toCommentTableFullDto(): CommentTableFullDto = CommentTableFullDto(\n  id = id.value,\n  articleId = articleId,\n  message = message,\n  userId = userId.value,\n)\n\npublic fun Iterable\u003cCommentTableDaoBase\u003e.toCommentTableFullDtoList(): List\u003cCommentTableFullDto\u003e = map {\n  it.toCommentTableFullDto()\n}\n\n```\n\n## Setup\n\nAdd KSP plugin to your module's `build.gradle.kts`:\n\n```kotlin\nplugins {\n    id(\"com.google.devtools.ksp\") version \"1.7.22-1.0.8\"\n}\n```\n\nAdd `Maven Central` to the repositories blocks in your project's `build.gradle.kts`:\n\n```kotlin\nrepositories {\n    mavenCentral()\n}\n```\n\nAdd `kesp` dependencies:\n\n```kotlin\ndependencies {\n    compileOnly(\"io.github.darkxanter.exposed:kesp-annotations:\u003cversion\u003e\")\n    ksp(\"io.github.darkxanter.exposed:kesp-processor:\u003cversion\u003e\")\n}\n```\n\nTo create DTO with the `kotlinx.serialization.Serializable` annotation, add to `build.gradle.kts`:\n\n```kotlin\nksp {\n    arg(\"kesp.kotlinxSerialization\", \"true\")\n}\n```\n\nGenerated source files are registered automatically since KSP 1.8.0-1.0.9. If you're using KSP 1.0.9 or newer and don't need to make the IDE aware of generated resources, feel free to skip following instructions.\n\nTo access generated code from KSP (before 1.8.0-1.0.9), you need to set up the source path into your module's `build.gradle.kts` file:\n\n```kotlin\nsourceSets.configureEach {\n    kotlin.srcDir(\"$buildDir/generated/ksp/$name/kotlin/\")\n}\n```\n---\n\nLicense\n======\n    Copyright 2022-2024 Sergey Shumov\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n           http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkxanter%2Fkesp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdarkxanter%2Fkesp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdarkxanter%2Fkesp/lists"}