[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DAGMX90NPYQZBPC6FE7JJ3":3,"skill-same:01M6DAGMX90NPYQZBPC6FE7JJ3":93},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":14,"repositoryHtmlUrl":40,"repositoryHomepageUrl":6,"ownerHtmlUrl":41,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":42,"install":43,"resources":56},"01M6DAGMX90NPYQZBPC6FE7JJ3","kotlin-exposed-patterns",null,"JetBrains Exposed ORM patterns including DSL queries, DAO pattern, transactions, HikariCP connection pooling, Flyway migrations, and repository pattern.","Prekzursil\u002Fagent-skills-toolchain","Prekzursil","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F54636077?v=4","01M6DACZ1PCQHD99SCR1YXG28B","Andrei Mihai Visalon","01M6DACZ670WZF535V3WYYSS4Z",0,"MIT","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Flibrary\u002Fskills\u002Fkotlin-exposed-patterns\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30],"kotlin exposed patterns","kotlin","patterns","repository","transactions","orm","queries","migrations","pattern","connection pooling","jetbrains","dao","2026-11-10 10:12:30",false,"---\nname: kotlin-exposed-patterns\ndescription: JetBrains Exposed ORM patterns including DSL queries, DAO pattern, transactions, HikariCP connection pooling, Flyway migrations, and repository pattern.\norigin: ECC\n---\n\n# Kotlin Exposed Patterns\n\nComprehensive patterns for database access with JetBrains Exposed ORM, including DSL queries, DAO, transactions, and production-ready configuration.\n\n## When to Use\n\n- Setting up database access with Exposed\n- Writing SQL queries using Exposed DSL or DAO\n- Configuring connection pooling with HikariCP\n- Creating database migrations with Flyway\n- Implementing the repository pattern with Exposed\n- Handling JSON columns and complex queries\n\n## How It Works\n\nExposed provides two query styles: DSL for direct SQL-like expressions and DAO for entity lifecycle management. HikariCP manages a pool of reusable database connections configured via `HikariConfig`. Flyway runs versioned SQL migration scripts at startup to keep the schema in sync. All database operations run inside `newSuspendedTransaction` blocks for coroutine safety and atomicity. The repository pattern wraps Exposed queries behind an interface so business logic stays decoupled from the data layer and tests can use an in-memory H2 database.\n\n## Examples\n\n### DSL Query\n\n```kotlin\nsuspend fun findUserById(id: UUID): UserRow? =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { UsersTable.id eq id }\n            .map { it.toUser() }\n            .singleOrNull()\n    }\n```\n\n### DAO Entity Usage\n\n```kotlin\nsuspend fun createUser(request: CreateUserRequest): User =\n    newSuspendedTransaction {\n        UserEntity.new {\n            name = request.name\n            email = request.email\n            role = request.role\n        }.toModel()\n    }\n```\n\n### HikariCP Configuration\n\n```kotlin\nval hikariConfig = HikariConfig().apply {\n    driverClassName = config.driver\n    jdbcUrl = config.url\n    username = config.username\n    password = config.password\n    maximumPoolSize = config.maxPoolSize\n    isAutoCommit = false\n    transactionIsolation = \"TRANSACTION_READ_COMMITTED\"\n    validate()\n}\n```\n\n## Database Setup\n\n### HikariCP Connection Pooling\n\n```kotlin\n\u002F\u002F DatabaseFactory.kt\nobject DatabaseFactory {\n    fun create(config: DatabaseConfig): Database {\n        val hikariConfig = HikariConfig().apply {\n            driverClassName = config.driver\n            jdbcUrl = config.url\n            username = config.username\n            password = config.password\n            maximumPoolSize = config.maxPoolSize\n            isAutoCommit = false\n            transactionIsolation = \"TRANSACTION_READ_COMMITTED\"\n            validate()\n        }\n\n        return Database.connect(HikariDataSource(hikariConfig))\n    }\n}\n\ndata class DatabaseConfig(\n    val url: String,\n    val driver: String = \"org.postgresql.Driver\",\n    val username: String = \"\",\n    val password: String = \"\",\n    val maxPoolSize: Int = 10,\n)\n```\n\n### Flyway Migrations\n\n```kotlin\n\u002F\u002F FlywayMigration.kt\nfun runMigrations(config: DatabaseConfig) {\n    Flyway.configure()\n        .dataSource(config.url, config.username, config.password)\n        .locations(\"classpath:db\u002Fmigration\")\n        .baselineOnMigrate(true)\n        .load()\n        .migrate()\n}\n\n\u002F\u002F Application startup\nfun Application.module() {\n    val config = DatabaseConfig(\n        url = environment.config.property(\"database.url\").getString(),\n        username = environment.config.property(\"database.username\").getString(),\n        password = environment.config.property(\"database.password\").getString(),\n    )\n    runMigrations(config)\n    val database = DatabaseFactory.create(config)\n    \u002F\u002F ...\n}\n```\n\n### Migration Files\n\n```sql\n-- src\u002Fmain\u002Fresources\u002Fdb\u002Fmigration\u002FV1__create_users.sql\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    name VARCHAR(100) NOT NULL,\n    email VARCHAR(255) NOT NULL UNIQUE,\n    role VARCHAR(20) NOT NULL DEFAULT 'USER',\n    metadata JSONB,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE INDEX idx_users_email ON users(email);\nCREATE INDEX idx_users_role ON users(role);\n```\n\n## Table Definitions\n\n### DSL Style Tables\n\n```kotlin\n\u002F\u002F tables\u002FUsersTable.kt\nobject UsersTable : UUIDTable(\"users\") {\n    val name = varchar(\"name\", 100)\n    val email = varchar(\"email\", 255).uniqueIndex()\n    val role = enumerationByName\u003CRole>(\"role\", 20)\n    val metadata = jsonb\u003CUserMetadata>(\"metadata\", Json.Default).nullable()\n    val createdAt = timestampWithTimeZone(\"created_at\").defaultExpression(CurrentTimestampWithTimeZone)\n    val updatedAt = timestampWithTimeZone(\"updated_at\").defaultExpression(CurrentTimestampWithTimeZone)\n}\n\nobject OrdersTable : UUIDTable(\"orders\") {\n    val userId = uuid(\"user_id\").references(UsersTable.id)\n    val status = enumerationByName\u003COrderStatus>(\"status\", 20)\n    val totalAmount = long(\"total_amount\")\n    val currency = varchar(\"currency\", 3)\n    val createdAt = timestampWithTimeZone(\"created_at\").defaultExpression(CurrentTimestampWithTimeZone)\n}\n\nobject OrderItemsTable : UUIDTable(\"order_items\") {\n    val orderId = uuid(\"order_id\").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)\n    val productId = uuid(\"product_id\")\n    val quantity = integer(\"quantity\")\n    val unitPrice = long(\"unit_price\")\n}\n```\n\n### Composite Tables\n\n```kotlin\nobject UserRolesTable : Table(\"user_roles\") {\n    val userId = uuid(\"user_id\").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)\n    val roleId = uuid(\"role_id\").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)\n    override val primaryKey = PrimaryKey(userId, roleId)\n}\n```\n\n## DSL Queries\n\n### Basic CRUD\n\n```kotlin\n\u002F\u002F Insert\nsuspend fun insertUser(name: String, email: String, role: Role): UUID =\n    newSuspendedTransaction {\n        UsersTable.insertAndGetId {\n            it[UsersTable.name] = name\n            it[UsersTable.email] = email\n            it[UsersTable.role] = role\n        }.value\n    }\n\n\u002F\u002F Select by ID\nsuspend fun findUserById(id: UUID): UserRow? =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { UsersTable.id eq id }\n            .map { it.toUser() }\n            .singleOrNull()\n    }\n\n\u002F\u002F Select with conditions\nsuspend fun findActiveAdmins(): List\u003CUserRow> =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { (UsersTable.role eq Role.ADMIN) }\n            .orderBy(UsersTable.name)\n            .map { it.toUser() }\n    }\n\n\u002F\u002F Update\nsuspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =\n    newSuspendedTransaction {\n        UsersTable.update({ UsersTable.id eq id }) {\n            it[email] = newEmail\n            it[updatedAt] = CurrentTimestampWithTimeZone\n        } > 0\n    }\n\n\u002F\u002F Delete\nsuspend fun deleteUser(id: UUID): Boolean =\n    newSuspendedTransaction {\n        UsersTable.deleteWhere { UsersTable.id eq id } > 0\n    }\n\n\u002F\u002F Row mapping\nprivate fun ResultRow.toUser() = UserRow(\n    id = this[UsersTable.id].value,\n    name = this[UsersTable.name],\n    email = this[UsersTable.email],\n    role = this[UsersTable.role],\n    metadata = this[UsersTable.metadata],\n    createdAt = this[UsersTable.createdAt],\n    updatedAt = this[UsersTable.updatedAt],\n)\n```\n\n### Advanced Queries\n\n```kotlin\n\u002F\u002F Join queries\nsuspend fun findOrdersWithUser(userId: UUID): List\u003COrderWithUser> =\n    newSuspendedTransaction {\n        (OrdersTable innerJoin UsersTable)\n            .selectAll()\n            .where { OrdersTable.userId eq userId }\n            .orderBy(OrdersTable.createdAt, SortOrder.DESC)\n            .map { row ->\n                OrderWithUser(\n                    orderId = row[OrdersTable.id].value,\n                    status = row[OrdersTable.status],\n                    totalAmount = row[OrdersTable.totalAmount],\n                    userName = row[UsersTable.name],\n                )\n            }\n    }\n\n\u002F\u002F Aggregation\nsuspend fun countUsersByRole(): Map\u003CRole, Long> =\n    newSuspendedTransaction {\n        UsersTable\n            .select(UsersTable.role, UsersTable.id.count())\n            .groupBy(UsersTable.role)\n            .associate { row ->\n                row[UsersTable.role] to row[UsersTable.id.count()]\n            }\n    }\n\n\u002F\u002F Subqueries\nsuspend fun findUsersWithOrders(): List\u003CUserRow> =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where {\n                UsersTable.id inSubQuery\n                    OrdersTable.select(OrdersTable.userId).withDistinct()\n            }\n            .map { it.toUser() }\n    }\n\n\u002F\u002F LIKE and pattern matching — always escape user input to prevent wildcard injection\nprivate fun escapeLikePattern(input: String): String =\n    input.replace(\"\\\\\", \"\\\\\\\\\").replace(\"%\", \"\\\\%\").replace(\"_\", \"\\\\_\")\n\nsuspend fun searchUsers(query: String): List\u003CUserRow> =\n    newSuspendedTransaction {\n        val sanitized = escapeLikePattern(query.lowercase())\n        UsersTable.selectAll()\n            .where {\n                (UsersTable.name.lowerCase() like \"%${sanitized}%\") or\n                    (UsersTable.email.lowerCase() like \"%${sanitized}%\")\n            }\n            .map { it.toUser() }\n    }\n```\n\n### Pagination\n\n```kotlin\ndata class Page\u003CT>(\n    val data: List\u003CT>,\n    val total: Long,\n    val page: Int,\n    val limit: Int,\n) {\n    val totalPages: Int get() = ((total + limit - 1) \u002F limit).toInt()\n    val hasNext: Boolean get() = page \u003C totalPages\n    val hasPrevious: Boolean get() = page > 1\n}\n\nsuspend fun findUsersPaginated(page: Int, limit: Int): Page\u003CUserRow> =\n    newSuspendedTransaction {\n        val total = UsersTable.selectAll().count()\n        val data = UsersTable.selectAll()\n            .orderBy(UsersTable.createdAt, SortOrder.DESC)\n            .limit(limit)\n            .offset(((page - 1) * limit).toLong())\n            .map { it.toUser() }\n\n        Page(data = data, total = total, page = page, limit = limit)\n    }\n```\n\n### Batch Operations\n\n```kotlin\n\u002F\u002F Batch insert\nsuspend fun insertUsers(users: List\u003CCreateUserRequest>): List\u003CUUID> =\n    newSuspendedTransaction {\n        UsersTable.batchInsert(users) { user ->\n            this[UsersTable.name] = user.name\n            this[UsersTable.email] = user.email\n            this[UsersTable.role] = user.role\n        }.map { it[UsersTable.id].value }\n    }\n\n\u002F\u002F Upsert (insert or update on conflict)\nsuspend fun upsertUser(id: UUID, name: String, email: String) {\n    newSuspendedTransaction {\n        UsersTable.upsert(UsersTable.email) {\n            it[UsersTable.id] = EntityID(id, UsersTable)\n            it[UsersTable.name] = name\n            it[UsersTable.email] = email\n            it[updatedAt] = CurrentTimestampWithTimeZone\n        }\n    }\n}\n```\n\n## DAO Pattern\n\n### Entity Definitions\n\n```kotlin\n\u002F\u002F entities\u002FUserEntity.kt\nclass UserEntity(id: EntityID\u003CUUID>) : UUIDEntity(id) {\n    companion object : UUIDEntityClass\u003CUserEntity>(UsersTable)\n\n    var name by UsersTable.name\n    var email by UsersTable.email\n    var role by UsersTable.role\n    var metadata by UsersTable.metadata\n    var createdAt by UsersTable.createdAt\n    var updatedAt by UsersTable.updatedAt\n\n    val orders by OrderEntity referrersOn OrdersTable.userId\n\n    fun toModel(): User = User(\n        id = id.value,\n        name = name,\n        email = email,\n        role = role,\n        metadata = metadata,\n        createdAt = createdAt,\n        updatedAt = updatedAt,\n    )\n}\n\nclass OrderEntity(id: EntityID\u003CUUID>) : UUIDEntity(id) {\n    companion object : UUIDEntityClass\u003COrderEntity>(OrdersTable)\n\n    var user by UserEntity referencedOn OrdersTable.userId\n    var status by OrdersTable.status\n    var totalAmount by OrdersTable.totalAmount\n    var currency by OrdersTable.currency\n    var createdAt by OrdersTable.createdAt\n\n    val items by OrderItemEntity referrersOn OrderItemsTable.orderId\n}\n```\n\n### DAO Operations\n\n```kotlin\nsuspend fun findUserByEmail(email: String): User? =\n    newSuspendedTransaction {\n        UserEntity.find { UsersTable.email eq email }\n            .firstOrNull()\n            ?.toModel()\n    }\n\nsuspend fun createUser(request: CreateUserRequest): User =\n    newSuspendedTransaction {\n        UserEntity.new {\n            name = request.name\n            email = request.email\n            role = request.role\n        }.toModel()\n    }\n\nsuspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =\n    newSuspendedTransaction {\n        UserEntity.findById(id)?.apply {\n            request.name?.let { name = it }\n            request.email?.let { email = it }\n            updatedAt = OffsetDateTime.now(ZoneOffset.UTC)\n        }?.toModel()\n    }\n```\n\n## Transactions\n\n### Suspend Transaction Support\n\n```kotlin\n\u002F\u002F Good: Use newSuspendedTransaction for coroutine support\nsuspend fun performDatabaseOperation(): Result\u003CUser> =\n    runCatching {\n        newSuspendedTransaction {\n            val user = UserEntity.new {\n                name = \"Alice\"\n                email = \"alice@example.com\"\n            }\n            \u002F\u002F All operations in this block are atomic\n            user.toModel()\n        }\n    }\n\n\u002F\u002F Good: Nested transactions with savepoints\nsuspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {\n    newSuspendedTransaction {\n        val from = UserEntity.findById(fromId) ?: throw NotFoundException(\"User $fromId not found\")\n        val to = UserEntity.findById(toId) ?: throw NotFoundException(\"User $toId not found\")\n\n        \u002F\u002F Debit\n        from.balance -= amount\n        \u002F\u002F Credit\n        to.balance += amount\n\n        \u002F\u002F Both succeed or both fail\n    }\n}\n```\n\n### Transaction Isolation\n\n```kotlin\nsuspend fun readCommittedQuery(): List\u003CUser> =\n    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {\n        UserEntity.all().map { it.toModel() }\n    }\n\nsuspend fun serializableOperation() {\n    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {\n        \u002F\u002F Strictest isolation level for critical operations\n    }\n}\n```\n\n## Repository Pattern\n\n### Interface Definition\n\n```kotlin\ninterface UserRepository {\n    suspend fun findById(id: UUID): User?\n    suspend fun findByEmail(email: String): User?\n    suspend fun findAll(page: Int, limit: Int): Page\u003CUser>\n    suspend fun search(query: String): List\u003CUser>\n    suspend fun create(request: CreateUserRequest): User\n    suspend fun update(id: UUID, request: UpdateUserRequest): User?\n    suspend fun delete(id: UUID): Boolean\n    suspend fun count(): Long\n}\n```\n\n### Exposed Implementation\n\n```kotlin\nclass ExposedUserRepository(\n    private val database: Database,\n) : UserRepository {\n\n    override suspend fun findById(id: UUID): User? =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll()\n                .where { UsersTable.id eq id }\n                .map { it.toUser() }\n                .singleOrNull()\n        }\n\n    override suspend fun findByEmail(email: String): User? =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll()\n                .where { UsersTable.email eq email }\n                .map { it.toUser() }\n                .singleOrNull()\n        }\n\n    override suspend fun findAll(page: Int, limit: Int): Page\u003CUser> =\n        newSuspendedTransaction(db = database) {\n            val total = UsersTable.selectAll().count()\n            val data = UsersTable.selectAll()\n                .orderBy(UsersTable.createdAt, SortOrder.DESC)\n                .limit(limit)\n                .offset(((page - 1) * limit).toLong())\n                .map { it.toUser() }\n            Page(data = data, total = total, page = page, limit = limit)\n        }\n\n    override suspend fun search(query: String): List\u003CUser> =\n        newSuspendedTransaction(db = database) {\n            val sanitized = escapeLikePattern(query.lowercase())\n            UsersTable.selectAll()\n                .where {\n                    (UsersTable.name.lowerCase() like \"%${sanitized}%\") or\n                        (UsersTable.email.lowerCase() like \"%${sanitized}%\")\n                }\n                .orderBy(UsersTable.name)\n                .map { it.toUser() }\n        }\n\n    override suspend fun create(request: CreateUserRequest): User =\n        newSuspendedTransaction(db = database) {\n            UsersTable.insert {\n                it[name] = request.name\n                it[email] = request.email\n                it[role] = request.role\n            }.resultedValues!!.first().toUser()\n        }\n\n    override suspend fun update(id: UUID, request: UpdateUserRequest): User? =\n        newSuspendedTransaction(db = database) {\n            val updated = UsersTable.update({ UsersTable.id eq id }) {\n                request.name?.let { name -> it[UsersTable.name] = name }\n                request.email?.let { email -> it[UsersTable.email] = email }\n                it[updatedAt] = CurrentTimestampWithTimeZone\n            }\n            if (updated > 0) findById(id) else null\n        }\n\n    override suspend fun delete(id: UUID): Boolean =\n        newSuspendedTransaction(db = database) {\n            UsersTable.deleteWhere { UsersTable.id eq id } > 0\n        }\n\n    override suspend fun count(): Long =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll().count()\n        }\n\n    private fun ResultRow.toUser() = User(\n        id = this[UsersTable.id].value,\n        name = this[UsersTable.name],\n        email = this[UsersTable.email],\n        role = this[UsersTable.role],\n        metadata = this[UsersTable.metadata],\n        createdAt = this[UsersTable.createdAt],\n        updatedAt = this[UsersTable.updatedAt],\n    )\n}\n```\n\n## JSON Columns\n\n### JSONB with kotlinx.serialization\n\n```kotlin\n\u002F\u002F Custom column type for JSONB\ninline fun \u003Creified T : Any> Table.jsonb(\n    name: String,\n    json: Json,\n): Column\u003CT> = registerColumn(name, object : ColumnType\u003CT>() {\n    override fun sqlType() = \"JSONB\"\n\n    override fun valueFromDB(value: Any): T = when (value) {\n        is String -> json.decodeFromString(value)\n        is PGobject -> {\n            val jsonString = value.value\n                ?: throw IllegalArgumentException(\"PGobject value is null for column '$name'\")\n            json.decodeFromString(jsonString)\n        }\n        else -> throw IllegalArgumentException(\"Unexpected value: $value\")\n    }\n\n    override fun notNullValueToDB(value: T): Any =\n        PGobject().apply {\n            type = \"jsonb\"\n            this.value = json.encodeToString(value)\n        }\n})\n\n\u002F\u002F Usage in table\n@Serializable\ndata class UserMetadata(\n    val preferences: Map\u003CString, String> = emptyMap(),\n    val tags: List\u003CString> = emptyList(),\n)\n\nobject UsersTable : UUIDTable(\"users\") {\n    val metadata = jsonb\u003CUserMetadata>(\"metadata\", Json.Default).nullable()\n}\n```\n\n## Testing with Exposed\n\n### In-Memory Database for Tests\n\n```kotlin\nclass UserRepositoryTest : FunSpec({\n    lateinit var database: Database\n    lateinit var repository: UserRepository\n\n    beforeSpec {\n        database = Database.connect(\n            url = \"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL\",\n            driver = \"org.h2.Driver\",\n        )\n        transaction(database) {\n            SchemaUtils.create(UsersTable)\n        }\n        repository = ExposedUserRepository(database)\n    }\n\n    beforeTest {\n        transaction(database) {\n            UsersTable.deleteAll()\n        }\n    }\n\n    test(\"create and find user\") {\n        val user = repository.create(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n\n        user.name shouldBe \"Alice\"\n        user.email shouldBe \"alice@example.com\"\n\n        val found = repository.findById(user.id)\n        found shouldBe user\n    }\n\n    test(\"findByEmail returns null for unknown email\") {\n        val result = repository.findByEmail(\"unknown@example.com\")\n        result.shouldBeNull()\n    }\n\n    test(\"pagination works correctly\") {\n        repeat(25) { i ->\n            repository.create(CreateUserRequest(\"User $i\", \"user$i@example.com\"))\n        }\n\n        val page1 = repository.findAll(page = 1, limit = 10)\n        page1.data shouldHaveSize 10\n        page1.total shouldBe 25\n        page1.hasNext shouldBe true\n\n        val page3 = repository.findAll(page = 3, limit = 10)\n        page3.data shouldHaveSize 5\n        page3.hasNext shouldBe false\n    }\n})\n```\n\n## Gradle Dependencies\n\n```kotlin\n\u002F\u002F build.gradle.kts\ndependencies {\n    \u002F\u002F Exposed\n    implementation(\"org.jetbrains.exposed:exposed-core:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-dao:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-jdbc:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-json:1.0.0\")\n\n    \u002F\u002F Database driver\n    implementation(\"org.postgresql:postgresql:42.7.5\")\n\n    \u002F\u002F Connection pooling\n    implementation(\"com.zaxxer:HikariCP:6.2.1\")\n\n    \u002F\u002F Migrations\n    implementation(\"org.flywaydb:flyway-core:10.22.0\")\n    implementation(\"org.flywaydb:flyway-database-postgresql:10.22.0\")\n\n    \u002F\u002F Testing\n    testImplementation(\"com.h2database:h2:2.3.232\")\n}\n```\n\n## Quick Reference: Exposed Patterns\n\n| Pattern | Description |\n|---------|-------------|\n| `object Table : UUIDTable(\"name\")` | Define table with UUID primary key |\n| `newSuspendedTransaction { }` | Coroutine-safe transaction block |\n| `Table.selectAll().where { }` | Query with conditions |\n| `Table.insertAndGetId { }` | Insert and return generated ID |\n| `Table.update({ condition }) { }` | Update matching rows |\n| `Table.deleteWhere { }` | Delete matching rows |\n| `Table.batchInsert(items) { }` | Efficient bulk insert |\n| `innerJoin` \u002F `leftJoin` | Join tables |\n| `orderBy` \u002F `limit` \u002F `offset` | Sort and paginate |\n| `count()` \u002F `sum()` \u002F `avg()` | Aggregation functions |\n\n**Remember**: Use the DSL style for simple queries and the DAO style when you need entity lifecycle management. Always use `newSuspendedTransaction` for coroutine support, and wrap database operations behind a repository interface for testability.\n","# Kotlin Exposed Patterns\n\nComprehensive patterns for database access with JetBrains Exposed ORM, including DSL queries, DAO, transactions, and production-ready configuration.\n\n## When to Use\n\n- Setting up database access with Exposed\n- Writing SQL queries using Exposed DSL or DAO\n- Configuring connection pooling with HikariCP\n- Creating database migrations with Flyway\n- Implementing the repository pattern with Exposed\n- Handling JSON columns and complex queries\n\n## How It Works\n\nExposed provides two query styles: DSL for direct SQL-like expressions and DAO for entity lifecycle management. HikariCP manages a pool of reusable database connections configured via `HikariConfig`. Flyway runs versioned SQL migration scripts at startup to keep the schema in sync. All database operations run inside `newSuspendedTransaction` blocks for coroutine safety and atomicity. The repository pattern wraps Exposed queries behind an interface so business logic stays decoupled from the data layer and tests can use an in-memory H2 database.\n\n## Examples\n\n### DSL Query\n\n```kotlin\nsuspend fun findUserById(id: UUID): UserRow? =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { UsersTable.id eq id }\n            .map { it.toUser() }\n            .singleOrNull()\n    }\n```\n\n### DAO Entity Usage\n\n```kotlin\nsuspend fun createUser(request: CreateUserRequest): User =\n    newSuspendedTransaction {\n        UserEntity.new {\n            name = request.name\n            email = request.email\n            role = request.role\n        }.toModel()\n    }\n```\n\n### HikariCP Configuration\n\n```kotlin\nval hikariConfig = HikariConfig().apply {\n    driverClassName = config.driver\n    jdbcUrl = config.url\n    username = config.username\n    password = config.password\n    maximumPoolSize = config.maxPoolSize\n    isAutoCommit = false\n    transactionIsolation = \"TRANSACTION_READ_COMMITTED\"\n    validate()\n}\n```\n\n## Database Setup\n\n### HikariCP Connection Pooling\n\n```kotlin\n\u002F\u002F DatabaseFactory.kt\nobject DatabaseFactory {\n    fun create(config: DatabaseConfig): Database {\n        val hikariConfig = HikariConfig().apply {\n            driverClassName = config.driver\n            jdbcUrl = config.url\n            username = config.username\n            password = config.password\n            maximumPoolSize = config.maxPoolSize\n            isAutoCommit = false\n            transactionIsolation = \"TRANSACTION_READ_COMMITTED\"\n            validate()\n        }\n\n        return Database.connect(HikariDataSource(hikariConfig))\n    }\n}\n\ndata class DatabaseConfig(\n    val url: String,\n    val driver: String = \"org.postgresql.Driver\",\n    val username: String = \"\",\n    val password: String = \"\",\n    val maxPoolSize: Int = 10,\n)\n```\n\n### Flyway Migrations\n\n```kotlin\n\u002F\u002F FlywayMigration.kt\nfun runMigrations(config: DatabaseConfig) {\n    Flyway.configure()\n        .dataSource(config.url, config.username, config.password)\n        .locations(\"classpath:db\u002Fmigration\")\n        .baselineOnMigrate(true)\n        .load()\n        .migrate()\n}\n\n\u002F\u002F Application startup\nfun Application.module() {\n    val config = DatabaseConfig(\n        url = environment.config.property(\"database.url\").getString(),\n        username = environment.config.property(\"database.username\").getString(),\n        password = environment.config.property(\"database.password\").getString(),\n    )\n    runMigrations(config)\n    val database = DatabaseFactory.create(config)\n    \u002F\u002F ...\n}\n```\n\n### Migration Files\n\n```sql\n-- src\u002Fmain\u002Fresources\u002Fdb\u002Fmigration\u002FV1__create_users.sql\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n    name VARCHAR(100) NOT NULL,\n    email VARCHAR(255) NOT NULL UNIQUE,\n    role VARCHAR(20) NOT NULL DEFAULT 'USER',\n    metadata JSONB,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),\n    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n);\n\nCREATE INDEX idx_users_email ON users(email);\nCREATE INDEX idx_users_role ON users(role);\n```\n\n## Table Definitions\n\n### DSL Style Tables\n\n```kotlin\n\u002F\u002F tables\u002FUsersTable.kt\nobject UsersTable : UUIDTable(\"users\") {\n    val name = varchar(\"name\", 100)\n    val email = varchar(\"email\", 255).uniqueIndex()\n    val role = enumerationByName\u003CRole>(\"role\", 20)\n    val metadata = jsonb\u003CUserMetadata>(\"metadata\", Json.Default).nullable()\n    val createdAt = timestampWithTimeZone(\"created_at\").defaultExpression(CurrentTimestampWithTimeZone)\n    val updatedAt = timestampWithTimeZone(\"updated_at\").defaultExpression(CurrentTimestampWithTimeZone)\n}\n\nobject OrdersTable : UUIDTable(\"orders\") {\n    val userId = uuid(\"user_id\").references(UsersTable.id)\n    val status = enumerationByName\u003COrderStatus>(\"status\", 20)\n    val totalAmount = long(\"total_amount\")\n    val currency = varchar(\"currency\", 3)\n    val createdAt = timestampWithTimeZone(\"created_at\").defaultExpression(CurrentTimestampWithTimeZone)\n}\n\nobject OrderItemsTable : UUIDTable(\"order_items\") {\n    val orderId = uuid(\"order_id\").references(OrdersTable.id, onDelete = ReferenceOption.CASCADE)\n    val productId = uuid(\"product_id\")\n    val quantity = integer(\"quantity\")\n    val unitPrice = long(\"unit_price\")\n}\n```\n\n### Composite Tables\n\n```kotlin\nobject UserRolesTable : Table(\"user_roles\") {\n    val userId = uuid(\"user_id\").references(UsersTable.id, onDelete = ReferenceOption.CASCADE)\n    val roleId = uuid(\"role_id\").references(RolesTable.id, onDelete = ReferenceOption.CASCADE)\n    override val primaryKey = PrimaryKey(userId, roleId)\n}\n```\n\n## DSL Queries\n\n### Basic CRUD\n\n```kotlin\n\u002F\u002F Insert\nsuspend fun insertUser(name: String, email: String, role: Role): UUID =\n    newSuspendedTransaction {\n        UsersTable.insertAndGetId {\n            it[UsersTable.name] = name\n            it[UsersTable.email] = email\n            it[UsersTable.role] = role\n        }.value\n    }\n\n\u002F\u002F Select by ID\nsuspend fun findUserById(id: UUID): UserRow? =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { UsersTable.id eq id }\n            .map { it.toUser() }\n            .singleOrNull()\n    }\n\n\u002F\u002F Select with conditions\nsuspend fun findActiveAdmins(): List\u003CUserRow> =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where { (UsersTable.role eq Role.ADMIN) }\n            .orderBy(UsersTable.name)\n            .map { it.toUser() }\n    }\n\n\u002F\u002F Update\nsuspend fun updateUserEmail(id: UUID, newEmail: String): Boolean =\n    newSuspendedTransaction {\n        UsersTable.update({ UsersTable.id eq id }) {\n            it[email] = newEmail\n            it[updatedAt] = CurrentTimestampWithTimeZone\n        } > 0\n    }\n\n\u002F\u002F Delete\nsuspend fun deleteUser(id: UUID): Boolean =\n    newSuspendedTransaction {\n        UsersTable.deleteWhere { UsersTable.id eq id } > 0\n    }\n\n\u002F\u002F Row mapping\nprivate fun ResultRow.toUser() = UserRow(\n    id = this[UsersTable.id].value,\n    name = this[UsersTable.name],\n    email = this[UsersTable.email],\n    role = this[UsersTable.role],\n    metadata = this[UsersTable.metadata],\n    createdAt = this[UsersTable.createdAt],\n    updatedAt = this[UsersTable.updatedAt],\n)\n```\n\n### Advanced Queries\n\n```kotlin\n\u002F\u002F Join queries\nsuspend fun findOrdersWithUser(userId: UUID): List\u003COrderWithUser> =\n    newSuspendedTransaction {\n        (OrdersTable innerJoin UsersTable)\n            .selectAll()\n            .where { OrdersTable.userId eq userId }\n            .orderBy(OrdersTable.createdAt, SortOrder.DESC)\n            .map { row ->\n                OrderWithUser(\n                    orderId = row[OrdersTable.id].value,\n                    status = row[OrdersTable.status],\n                    totalAmount = row[OrdersTable.totalAmount],\n                    userName = row[UsersTable.name],\n                )\n            }\n    }\n\n\u002F\u002F Aggregation\nsuspend fun countUsersByRole(): Map\u003CRole, Long> =\n    newSuspendedTransaction {\n        UsersTable\n            .select(UsersTable.role, UsersTable.id.count())\n            .groupBy(UsersTable.role)\n            .associate { row ->\n                row[UsersTable.role] to row[UsersTable.id.count()]\n            }\n    }\n\n\u002F\u002F Subqueries\nsuspend fun findUsersWithOrders(): List\u003CUserRow> =\n    newSuspendedTransaction {\n        UsersTable.selectAll()\n            .where {\n                UsersTable.id inSubQuery\n                    OrdersTable.select(OrdersTable.userId).withDistinct()\n            }\n            .map { it.toUser() }\n    }\n\n\u002F\u002F LIKE and pattern matching — always escape user input to prevent wildcard injection\nprivate fun escapeLikePattern(input: String): String =\n    input.replace(\"\\\\\", \"\\\\\\\\\").replace(\"%\", \"\\\\%\").replace(\"_\", \"\\\\_\")\n\nsuspend fun searchUsers(query: String): List\u003CUserRow> =\n    newSuspendedTransaction {\n        val sanitized = escapeLikePattern(query.lowercase())\n        UsersTable.selectAll()\n            .where {\n                (UsersTable.name.lowerCase() like \"%${sanitized}%\") or\n                    (UsersTable.email.lowerCase() like \"%${sanitized}%\")\n            }\n            .map { it.toUser() }\n    }\n```\n\n### Pagination\n\n```kotlin\ndata class Page\u003CT>(\n    val data: List\u003CT>,\n    val total: Long,\n    val page: Int,\n    val limit: Int,\n) {\n    val totalPages: Int get() = ((total + limit - 1) \u002F limit).toInt()\n    val hasNext: Boolean get() = page \u003C totalPages\n    val hasPrevious: Boolean get() = page > 1\n}\n\nsuspend fun findUsersPaginated(page: Int, limit: Int): Page\u003CUserRow> =\n    newSuspendedTransaction {\n        val total = UsersTable.selectAll().count()\n        val data = UsersTable.selectAll()\n            .orderBy(UsersTable.createdAt, SortOrder.DESC)\n            .limit(limit)\n            .offset(((page - 1) * limit).toLong())\n            .map { it.toUser() }\n\n        Page(data = data, total = total, page = page, limit = limit)\n    }\n```\n\n### Batch Operations\n\n```kotlin\n\u002F\u002F Batch insert\nsuspend fun insertUsers(users: List\u003CCreateUserRequest>): List\u003CUUID> =\n    newSuspendedTransaction {\n        UsersTable.batchInsert(users) { user ->\n            this[UsersTable.name] = user.name\n            this[UsersTable.email] = user.email\n            this[UsersTable.role] = user.role\n        }.map { it[UsersTable.id].value }\n    }\n\n\u002F\u002F Upsert (insert or update on conflict)\nsuspend fun upsertUser(id: UUID, name: String, email: String) {\n    newSuspendedTransaction {\n        UsersTable.upsert(UsersTable.email) {\n            it[UsersTable.id] = EntityID(id, UsersTable)\n            it[UsersTable.name] = name\n            it[UsersTable.email] = email\n            it[updatedAt] = CurrentTimestampWithTimeZone\n        }\n    }\n}\n```\n\n## DAO Pattern\n\n### Entity Definitions\n\n```kotlin\n\u002F\u002F entities\u002FUserEntity.kt\nclass UserEntity(id: EntityID\u003CUUID>) : UUIDEntity(id) {\n    companion object : UUIDEntityClass\u003CUserEntity>(UsersTable)\n\n    var name by UsersTable.name\n    var email by UsersTable.email\n    var role by UsersTable.role\n    var metadata by UsersTable.metadata\n    var createdAt by UsersTable.createdAt\n    var updatedAt by UsersTable.updatedAt\n\n    val orders by OrderEntity referrersOn OrdersTable.userId\n\n    fun toModel(): User = User(\n        id = id.value,\n        name = name,\n        email = email,\n        role = role,\n        metadata = metadata,\n        createdAt = createdAt,\n        updatedAt = updatedAt,\n    )\n}\n\nclass OrderEntity(id: EntityID\u003CUUID>) : UUIDEntity(id) {\n    companion object : UUIDEntityClass\u003COrderEntity>(OrdersTable)\n\n    var user by UserEntity referencedOn OrdersTable.userId\n    var status by OrdersTable.status\n    var totalAmount by OrdersTable.totalAmount\n    var currency by OrdersTable.currency\n    var createdAt by OrdersTable.createdAt\n\n    val items by OrderItemEntity referrersOn OrderItemsTable.orderId\n}\n```\n\n### DAO Operations\n\n```kotlin\nsuspend fun findUserByEmail(email: String): User? =\n    newSuspendedTransaction {\n        UserEntity.find { UsersTable.email eq email }\n            .firstOrNull()\n            ?.toModel()\n    }\n\nsuspend fun createUser(request: CreateUserRequest): User =\n    newSuspendedTransaction {\n        UserEntity.new {\n            name = request.name\n            email = request.email\n            role = request.role\n        }.toModel()\n    }\n\nsuspend fun updateUser(id: UUID, request: UpdateUserRequest): User? =\n    newSuspendedTransaction {\n        UserEntity.findById(id)?.apply {\n            request.name?.let { name = it }\n            request.email?.let { email = it }\n            updatedAt = OffsetDateTime.now(ZoneOffset.UTC)\n        }?.toModel()\n    }\n```\n\n## Transactions\n\n### Suspend Transaction Support\n\n```kotlin\n\u002F\u002F Good: Use newSuspendedTransaction for coroutine support\nsuspend fun performDatabaseOperation(): Result\u003CUser> =\n    runCatching {\n        newSuspendedTransaction {\n            val user = UserEntity.new {\n                name = \"Alice\"\n                email = \"alice@example.com\"\n            }\n            \u002F\u002F All operations in this block are atomic\n            user.toModel()\n        }\n    }\n\n\u002F\u002F Good: Nested transactions with savepoints\nsuspend fun transferFunds(fromId: UUID, toId: UUID, amount: Long) {\n    newSuspendedTransaction {\n        val from = UserEntity.findById(fromId) ?: throw NotFoundException(\"User $fromId not found\")\n        val to = UserEntity.findById(toId) ?: throw NotFoundException(\"User $toId not found\")\n\n        \u002F\u002F Debit\n        from.balance -= amount\n        \u002F\u002F Credit\n        to.balance += amount\n\n        \u002F\u002F Both succeed or both fail\n    }\n}\n```\n\n### Transaction Isolation\n\n```kotlin\nsuspend fun readCommittedQuery(): List\u003CUser> =\n    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {\n        UserEntity.all().map { it.toModel() }\n    }\n\nsuspend fun serializableOperation() {\n    newSuspendedTransaction(transactionIsolation = Connection.TRANSACTION_SERIALIZABLE) {\n        \u002F\u002F Strictest isolation level for critical operations\n    }\n}\n```\n\n## Repository Pattern\n\n### Interface Definition\n\n```kotlin\ninterface UserRepository {\n    suspend fun findById(id: UUID): User?\n    suspend fun findByEmail(email: String): User?\n    suspend fun findAll(page: Int, limit: Int): Page\u003CUser>\n    suspend fun search(query: String): List\u003CUser>\n    suspend fun create(request: CreateUserRequest): User\n    suspend fun update(id: UUID, request: UpdateUserRequest): User?\n    suspend fun delete(id: UUID): Boolean\n    suspend fun count(): Long\n}\n```\n\n### Exposed Implementation\n\n```kotlin\nclass ExposedUserRepository(\n    private val database: Database,\n) : UserRepository {\n\n    override suspend fun findById(id: UUID): User? =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll()\n                .where { UsersTable.id eq id }\n                .map { it.toUser() }\n                .singleOrNull()\n        }\n\n    override suspend fun findByEmail(email: String): User? =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll()\n                .where { UsersTable.email eq email }\n                .map { it.toUser() }\n                .singleOrNull()\n        }\n\n    override suspend fun findAll(page: Int, limit: Int): Page\u003CUser> =\n        newSuspendedTransaction(db = database) {\n            val total = UsersTable.selectAll().count()\n            val data = UsersTable.selectAll()\n                .orderBy(UsersTable.createdAt, SortOrder.DESC)\n                .limit(limit)\n                .offset(((page - 1) * limit).toLong())\n                .map { it.toUser() }\n            Page(data = data, total = total, page = page, limit = limit)\n        }\n\n    override suspend fun search(query: String): List\u003CUser> =\n        newSuspendedTransaction(db = database) {\n            val sanitized = escapeLikePattern(query.lowercase())\n            UsersTable.selectAll()\n                .where {\n                    (UsersTable.name.lowerCase() like \"%${sanitized}%\") or\n                        (UsersTable.email.lowerCase() like \"%${sanitized}%\")\n                }\n                .orderBy(UsersTable.name)\n                .map { it.toUser() }\n        }\n\n    override suspend fun create(request: CreateUserRequest): User =\n        newSuspendedTransaction(db = database) {\n            UsersTable.insert {\n                it[name] = request.name\n                it[email] = request.email\n                it[role] = request.role\n            }.resultedValues!!.first().toUser()\n        }\n\n    override suspend fun update(id: UUID, request: UpdateUserRequest): User? =\n        newSuspendedTransaction(db = database) {\n            val updated = UsersTable.update({ UsersTable.id eq id }) {\n                request.name?.let { name -> it[UsersTable.name] = name }\n                request.email?.let { email -> it[UsersTable.email] = email }\n                it[updatedAt] = CurrentTimestampWithTimeZone\n            }\n            if (updated > 0) findById(id) else null\n        }\n\n    override suspend fun delete(id: UUID): Boolean =\n        newSuspendedTransaction(db = database) {\n            UsersTable.deleteWhere { UsersTable.id eq id } > 0\n        }\n\n    override suspend fun count(): Long =\n        newSuspendedTransaction(db = database) {\n            UsersTable.selectAll().count()\n        }\n\n    private fun ResultRow.toUser() = User(\n        id = this[UsersTable.id].value,\n        name = this[UsersTable.name],\n        email = this[UsersTable.email],\n        role = this[UsersTable.role],\n        metadata = this[UsersTable.metadata],\n        createdAt = this[UsersTable.createdAt],\n        updatedAt = this[UsersTable.updatedAt],\n    )\n}\n```\n\n## JSON Columns\n\n### JSONB with kotlinx.serialization\n\n```kotlin\n\u002F\u002F Custom column type for JSONB\ninline fun \u003Creified T : Any> Table.jsonb(\n    name: String,\n    json: Json,\n): Column\u003CT> = registerColumn(name, object : ColumnType\u003CT>() {\n    override fun sqlType() = \"JSONB\"\n\n    override fun valueFromDB(value: Any): T = when (value) {\n        is String -> json.decodeFromString(value)\n        is PGobject -> {\n            val jsonString = value.value\n                ?: throw IllegalArgumentException(\"PGobject value is null for column '$name'\")\n            json.decodeFromString(jsonString)\n        }\n        else -> throw IllegalArgumentException(\"Unexpected value: $value\")\n    }\n\n    override fun notNullValueToDB(value: T): Any =\n        PGobject().apply {\n            type = \"jsonb\"\n            this.value = json.encodeToString(value)\n        }\n})\n\n\u002F\u002F Usage in table\n@Serializable\ndata class UserMetadata(\n    val preferences: Map\u003CString, String> = emptyMap(),\n    val tags: List\u003CString> = emptyList(),\n)\n\nobject UsersTable : UUIDTable(\"users\") {\n    val metadata = jsonb\u003CUserMetadata>(\"metadata\", Json.Default).nullable()\n}\n```\n\n## Testing with Exposed\n\n### In-Memory Database for Tests\n\n```kotlin\nclass UserRepositoryTest : FunSpec({\n    lateinit var database: Database\n    lateinit var repository: UserRepository\n\n    beforeSpec {\n        database = Database.connect(\n            url = \"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=PostgreSQL\",\n            driver = \"org.h2.Driver\",\n        )\n        transaction(database) {\n            SchemaUtils.create(UsersTable)\n        }\n        repository = ExposedUserRepository(database)\n    }\n\n    beforeTest {\n        transaction(database) {\n            UsersTable.deleteAll()\n        }\n    }\n\n    test(\"create and find user\") {\n        val user = repository.create(CreateUserRequest(\"Alice\", \"alice@example.com\"))\n\n        user.name shouldBe \"Alice\"\n        user.email shouldBe \"alice@example.com\"\n\n        val found = repository.findById(user.id)\n        found shouldBe user\n    }\n\n    test(\"findByEmail returns null for unknown email\") {\n        val result = repository.findByEmail(\"unknown@example.com\")\n        result.shouldBeNull()\n    }\n\n    test(\"pagination works correctly\") {\n        repeat(25) { i ->\n            repository.create(CreateUserRequest(\"User $i\", \"user$i@example.com\"))\n        }\n\n        val page1 = repository.findAll(page = 1, limit = 10)\n        page1.data shouldHaveSize 10\n        page1.total shouldBe 25\n        page1.hasNext shouldBe true\n\n        val page3 = repository.findAll(page = 3, limit = 10)\n        page3.data shouldHaveSize 5\n        page3.hasNext shouldBe false\n    }\n})\n```\n\n## Gradle Dependencies\n\n```kotlin\n\u002F\u002F build.gradle.kts\ndependencies {\n    \u002F\u002F Exposed\n    implementation(\"org.jetbrains.exposed:exposed-core:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-dao:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-jdbc:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0\")\n    implementation(\"org.jetbrains.exposed:exposed-json:1.0.0\")\n\n    \u002F\u002F Database driver\n    implementation(\"org.postgresql:postgresql:42.7.5\")\n\n    \u002F\u002F Connection pooling\n    implementation(\"com.zaxxer:HikariCP:6.2.1\")\n\n    \u002F\u002F Migrations\n    implementation(\"org.flywaydb:flyway-core:10.22.0\")\n    implementation(\"org.flywaydb:flyway-database-postgresql:10.22.0\")\n\n    \u002F\u002F Testing\n    testImplementation(\"com.h2database:h2:2.3.232\")\n}\n```\n\n## Quick Reference: Exposed Patterns\n\n| Pattern | Description |\n|---------|-------------|\n| `object Table : UUIDTable(\"name\")` | Define table with UUID primary key |\n| `newSuspendedTransaction { }` | Coroutine-safe transaction block |\n| `Table.selectAll().where { }` | Query with conditions |\n| `Table.insertAndGetId { }` | Insert and return generated ID |\n| `Table.update({ condition }) { }` | Update matching rows |\n| `Table.deleteWhere { }` | Delete matching rows |\n| `Table.batchInsert(items) { }` | Efficient bulk insert |\n| `innerJoin` \u002F `leftJoin` | Join tables |\n| `orderBy` \u002F `limit` \u002F `offset` | Sort and paginate |\n| `count()` \u002F `sum()` \u002F `avg()` | Aggregation functions |\n\n**Remember**: Use the DSL style for simple queries and the DAO style when you need entity lifecycle management. Always use `newSuspendedTransaction` for coroutine support, and wrap database operations behind a repository interface for testability.\n",{"name":5,"origin":36,"description":7},"ECC","main","a298c929b6cbd662e1fce54302ce520d5ceea1a5",".agents\u002Flibrary\u002Fskills\u002Fkotlin-exposed-patterns","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain","https:\u002F\u002Fgithub.com\u002FPrekzursil","2026-11-02 14:30:57",{"source":8,"skillName":5,"commands":44,"prompt":54,"zipAvailable":55},[45,48,51],{"tool":46,"command":47},"npx","npx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-exposed-patterns",{"tool":49,"command":50},"bunx","bunx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-exposed-patterns",{"tool":52,"command":53},"pnpm","pnpm dlx skills add Prekzursil\u002Fagent-skills-toolchain --skill kotlin-exposed-patterns","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002F.agents\u002Flibrary\u002Fskills\u002Fkotlin-exposed-patterns\u002FSKILL.md",true,[57,63,69,73,77,81,85,89],{"path":58,"resourceType":59,"resourceTypeDesc":60,"sizeBytes":61,"sourceHtmlUrl":62},"customize.toml","OTHER","其他",1643,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fcustomize.toml",{"path":64,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":67,"sourceHtmlUrl":68},"steps\u002Fstep-01-document-discovery.md","REFERENCE","参考文档",5152,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-01-document-discovery.md",{"path":70,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":71,"sourceHtmlUrl":72},"steps\u002Fstep-02-gdd-analysis.md",4753,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-02-gdd-analysis.md",{"path":74,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":75,"sourceHtmlUrl":76},"steps\u002Fstep-03-epic-coverage-validation.md",4216,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-03-epic-coverage-validation.md",{"path":78,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":79,"sourceHtmlUrl":80},"steps\u002Fstep-04-ux-alignment.md",3502,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-04-ux-alignment.md",{"path":82,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":83,"sourceHtmlUrl":84},"steps\u002Fstep-05-epic-quality-review.md",6985,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-05-epic-quality-review.md",{"path":86,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":87,"sourceHtmlUrl":88},"steps\u002Fstep-06-final-assessment.md",3657,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Fsteps\u002Fstep-06-final-assessment.md",{"path":90,"resourceType":65,"resourceTypeDesc":66,"sizeBytes":91,"sourceHtmlUrl":92},"templates\u002Freadiness-report-template.md",95,"https:\u002F\u002Fgithub.com\u002Ftonygomes-wq\u002Fevo\u002Fblob\u002Fmain\u002F.kiro\u002Fskills\u002Fgds-check-implementation-readiness\u002Ftemplates\u002Freadiness-report-template.md",[94,116,147,174,188,204],{"skillNo":95,"name":96,"nameZh":6,"description":97,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":98,"categories":99,"keywords":100,"publishedAt":31},"01M6DF7D7478R55P46REYH4QAC","clarify-requirements","Resolve genuine pre-execution ambiguity by scanning repo context, generating 1-6 grounded options, and asking 1-3 focused questions via AskUserQuestion. NOT for mid-task use (injection risk); NOT when the goal is already unambiguous; use to disambiguate vague prompts before any file edits.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fworkspace-hooks\u002Fskills\u002Fclarify-requirements\u002FSKILL.md",[],[101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"clarify requirements","clarify","requirements","risk","options","task","prompts","goal","scanning","use","context","repo","injection","execution","file",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":127,"publishedAt":31},"01M6DF79CA4BHJS66J8EZDBPWJ","openmm-md","Run FREE, open-source, Windows-native GPU molecular dynamics via the openmm-md MCP (OpenMM + CUDA on the local NVIDIA GPU). Use for protein \u002F protein-ligand MD when you want a free stack or are on Windows without a Schrodinger license. Covers the full workflow (prepare, solvate, minimize, equilibrate, production, analyze, mmgbsa) AND an honest decision guide vs Schrodinger Desmond, including how to run Desmond legitimately on Linux or a cloud GPU with a real license. This is a deliberately LESSER free alternative and does NOT replace the schrodinger MCP tools. Do NOT use it to pick a free tool when a validated FEP+, WaterMap, IFD-MD, or Prime MM-GBSA result is actually required.","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Fmcp\u002Fopenmm-md-mcp\u002Fskill\u002Fopenmm-md\u002FSKILL.md",[122],{"categoryNo":123,"categoryName":124,"taxonomyType":125,"taxonomyTypeDesc":126},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[128,129,130,131,132,133,134,135,136,137,138,139,140,141,110,142,143,144,145,146],"openmm md","how","decision","pick","linux","analyze","tool","stack","open","NVIDIA","gpu","license","workflow","windows","free","molecular dynamics","local","do","MCP",{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":159,"publishedAt":31},"01M6DF76C1MG85VEWP8VZCBNMN","visual-review","Take screenshots of web pages and UI using Playwright for visual review and iteration","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fvisual-review\u002FSKILL.md",[153,156],{"categoryNo":154,"categoryName":155,"taxonomyType":125,"taxonomyTypeDesc":126},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":157,"categoryName":158,"taxonomyType":125,"taxonomyTypeDesc":126},"01JBSEEDCAT000000000000008","测试与质量",[160,161,162,163,164,165,166,167,168,169,129,170,171,172,173],"review","visual","visual review","pages","web","false","iteration","take","check","slides","ui","screenshot","playwright","screenshots",{"skillNo":175,"name":176,"nameZh":6,"description":177,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":178,"categories":179,"keywords":180,"publishedAt":31},"01M6DF73AQVCBZKEN0YXEX9FXP","status","Diagnostic status report — shows metaswarm installation state, project setup, and potential issues","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstatus\u002FSKILL.md",[],[176,181,182,183,184,185,186,187],"setup","report","project","installation","project setup","issues","state",{"skillNo":189,"name":190,"nameZh":6,"description":191,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":192,"categories":193,"keywords":195,"publishedAt":31},"01M6DF6X6C2XB0P6GX76P7TTYV","start","Use when starting work on any task, when the user mentions metaswarm, or when the user wants to begin tracked development work","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fstart\u002FSKILL.md",[194],{"categoryNo":123,"categoryName":124,"taxonomyType":125,"taxonomyTypeDesc":126},[190,196,106,197,198,199,110,200,201,202,203],"start issue","issue","ready","work on issue","user","work","work on","development",{"skillNo":205,"name":181,"nameZh":6,"description":206,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":207,"categories":208,"keywords":209,"publishedAt":31},"01M6DF6QMJ9AJKFKHHKRT005PQ","Interactive project setup — detects your project, configures metaswarm, writes project-local files","https:\u002F\u002Fgithub.com\u002FPrekzursil\u002Fagent-skills-toolchain\u002Fblob\u002Fmain\u002Ftools\u002Feditor\u002Fmetaswarm\u002Fskills\u002Fsetup\u002FSKILL.md",[],[181,183,144,210,185,211],"files","interactive"]