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)