Demo

Schema Migration System Demo

Closes #519

/claim #519

Summary

Implements a complete, algebraic, serializable Schema Migration System for ZIO Blocks, resolving issue #519.

Architecture

Two-layer design mirroring the existing DynamicPatch / Patch[A] pattern:

DynamicMigration - untyped, pure data, fully serializable

  • actions: Vector[MigrationAction] - a sequence of named operations applied in order
  • apply(DynamicValue): Either[MigrationError, DynamicValue]
  • reverse: DynamicMigration - structural inverse of all actions
  • ++ composition, isEmpty

Migration[A, B] - typed wrapper

  • Converts A to DynamicValue via sourceSchema, applies DynamicMigration, converts back to B via targetSchema
  • reverse: Migration[B, A]
  • ++[C](that: Migration[B, C]): Migration[A, C]

MigrationAction ADT

All actions carry at: DynamicOptic for precise path addressing:

  • AddField(at, default) - Add a field with a default value
  • DropField(at, defaultForReverse) - Remove a field
  • Rename(at, to) - Rename a field
  • TransformValue(at, expr) - Transform a value using a DynamicMigrationExpr
  • Mandate(at, default) - Make optional field mandatory
  • Optionalize(at) - Wrap mandatory field in Some
  • RenameCase(at, from, to) - Rename an enum variant
  • TransformCase(at, caseName, actions) - Apply nested migrations to a variant payload
  • TransformElements(at, expr) - Map over sequence elements
  • TransformKeys(at, expr) - Map over map keys
  • TransformValues(at, expr) - Map over map values

DynamicMigrationExpr

Serializable expression ADT: Identity, Constant, IntToLong/LongToInt, IntToString/StringToInt, LongToString/StringToLong, DoubleToString/StringToDouble, FloatToDouble/DoubleToFloat, BooleanToString/StringToBoolean, ConcatFields, Compose

MigrationBuilder DSL

val migration = Migration.newBuilder[PersonV1, PersonV2]
.renameField("name", "fullName")
.addField("country", Schema[String], "Unknown")
.build

.build validates paths, .buildPartial skips validation

Laws

  • Identity: Migration.identity[A].apply(a) == Right(a)
  • Associativity: (m1 ++ m2) ++ m3 == m1 ++ (m2 ++ m3)
  • Reverse: m.reverse.reverse has structurally equal actions to m

Files Added

  • migration/MigrationError.scala
  • migration/DynamicMigrationExpr.scala
  • migration/MigrationAction.scala
  • migration/DynamicMigration.scala
  • migration/Migration.scala
  • migration/MigrationBuilder.scala
  • migration/DynamicMigrationSpec.scala (tests)
  • migration/MigrationSpec.scala (tests)

Claim

Total prize pool $8,000
Total paid $0
Status Pending
Submitted March 22, 2026
Last updated March 22, 2026

Contributors

TA

Tanishq Shah

@tanishqshah

100%

Sponsors

MA

marianaguzmanguerrero16-dev

@marianaguzmanguerrero16-dev

$4,000
ZI

ZIO

@ZIO

$4,000