Hi again! Unfortunately it doesn't let me edit my ...
# ksp
r
Hi again! Unfortunately it doesn't let me edit my previous message (I believe it's too old to edit), but I do wish to add an example. KReplica allows you to specify what variants you want to create, and which properties should be included in which variants (via include/exclude). _ KReplica also supports unversioned DTOs, but say you want a versioned one; And this is version 1 of User Account. Note that typically I want the ID to be created by the database, so I want to exclude it from being patched/created on the Kotlin side.
Copy code
@Replicate.Model(variants = [DtoVariant.DATA, DtoVariant.PATCH, DtoVariant.CREATE])
private interface V1 : UserAccount {
    val name: Pair<String, String> // this is included in all the variants
    @Replicate.Property(include = [DtoVariant.DATA])
    val id: Int
    @Replicate.Property(exclude = [DtoVariant.CREATE])
    val banReason: String?
}
Now say you have Version 2 of User Account (notice we have nominal typing turned on which is another feature):
Copy code
@OptIn(ExperimentalUuidApi::class)
@Replicate.Model(
    variants = [DtoVariant.DATA, DtoVariant.PATCH],
    nominalTyping = NominalTyping.ENABLED
)
private interface V2 : UserAccount {
    val firstName: String
    val lastName: String
    @Replicate.Property(include = [DtoVariant.DATA], nominalTyping = NominalTyping.DISABLED)
    val id: Uuid
}
I will continue the rest in a thread so this message doesn't get too long.
This is what is generated by KReplica:
Copy code
// Generated by KReplica. Do not edit.
@file:OptIn(ExperimentalUuidApi::class)

package io.availe

import io.availe.models.Patchable
import <http://kotlin.Int|kotlin.Int>
import kotlin.OptIn
import kotlin.Pair
import kotlin.String
import kotlin.jvm.JvmInline
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

/**
 * A sealed interface hierarchy representing all versions of the UserAccount data model.
 */
public sealed interface UserAccountSchema {
  public sealed interface DataVariant : UserAccountSchema

  public sealed interface PatchRequestVariant : UserAccountSchema

  public sealed interface CreateRequestVariant : UserAccountSchema

  /**
   * --------------------------
   * |   Version 1 (V1)   |
   * --------------------------
   */
  public sealed interface V1 : UserAccountSchema {
    public data class Data(
      public val name: Pair<String, String>,
      public val id: Int,
      public val banReason: String?,
      public val schemaVersion: Int = 1,
    ) : V1,
        DataVariant

    public data class PatchRequest(
      public val name: Patchable<Pair<String, String>> = Patchable.Unchanged,
      public val banReason: Patchable<String?> = Patchable.Unchanged,
      public val schemaVersion: Patchable<Int> = Patchable.Unchanged,
    ) : V1,
        PatchRequestVariant

    public data class CreateRequest(
      public val name: Pair<String, String>,
      public val schemaVersion: Int = 1,
    ) : V1,
        CreateRequestVariant
  }

  /**
   * --------------------------
   * |   Version 2 (V2)   |
   * --------------------------
   */
  public sealed interface V2 : UserAccountSchema {
    public data class Data(
      public val firstName: UserAccountFirstName,
      public val lastName: UserAccountLastName,
      public val id: Uuid,
      public val schemaVersion: UserAccountSchemaVersion = UserAccountSchemaVersion(2),
    ) : V2,
        DataVariant

    public data class PatchRequest(
      public val firstName: Patchable<UserAccountFirstName> = Patchable.Unchanged,
      public val lastName: Patchable<UserAccountLastName> = Patchable.Unchanged,
      public val schemaVersion: Patchable<UserAccountSchemaVersion> = Patchable.Unchanged,
    ) : V2,
        PatchRequestVariant
  }
}

/**
 * SHARED VALUE CLASSES
 *
 * Type-safe wrappers for properties that are consistent across versions.
 */
@JvmInline
public value class UserAccountFirstName(
  public val `value`: String,
)

@JvmInline
public value class UserAccountLastName(
  public val `value`: String,
)

@JvmInline
public value class UserAccountSchemaVersion(
  public val `value`: Int,
)
__ Now this might seem a bit complex for DTOs, but the reason I really wanted to pursue this approach is for the exhaustive when statements it allows: For example, say that I want to target everything of a single version:
Copy code
fun handleUser(user: UserAccountSchema.V1) {
    when(user) {
        is UserAccountSchema.V1.Data -> TODO()
        is UserAccountSchema.V1.CreateRequest -> TODO()
        is UserAccountSchema.V1.PatchRequest -> TODO()
    }
}
Or say I want to target every data variant:
Copy code
fun handleUser(user: UserAccountSchema.DataVariant) {
    when(user) {
        is UserAccountSchema.V1.Data -> TODO()
        is UserAccountSchema.V2.Data -> TODO()
    }
}
Or perhaps I only want a single instance:
Copy code
fun handleUser(user: UserAccountSchema.V1.Data) {
    print(user.id)
}
_ Which I think is very nifty thing when you say have multiple versions of a DTO, and you can just trust on the compiler to force you handle every version. KReplica also has support for annotations. If you wish, you can even make annotations only affect certain variants via include/exclude system which
Replicate.Apply
supports (more info in the docs). It does also have support for serialization, There's a few caveats due to the interface approach but that's fully explained in the docs. Link to repo (again): https://github.com/AvaileDev/KReplica