@zio.dev/zio-blocks 0.0.33 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/guides/compile-time-resource-safety-with-scope.md +16 -17
- package/guides/getting-started-with-mux.md +1507 -0
- package/guides/query-dsl-extending.md +161 -102
- package/guides/query-dsl-fluent-builder.md +217 -157
- package/guides/query-dsl-reified-optics.md +12 -10
- package/guides/query-dsl-sql.md +246 -165
- package/guides/telemetry-guide.md +1069 -0
- package/guides/zio-schema-migration.md +29 -22
- package/index.md +292 -50
- package/package.json +1 -1
- package/plans/config-follow-up-prs.md +188 -0
- package/plans/config-pr-assessment-roadmap.md +310 -0
- package/reference/MuxDataFlow.jsx +250 -0
- package/reference/async.md +651 -0
- package/reference/chunk.md +3533 -308
- package/reference/codegen/case-class.md +436 -0
- package/reference/codegen/emitter-config.md +383 -0
- package/reference/codegen/examples.md +664 -0
- package/reference/codegen/field.md +316 -0
- package/reference/codegen/index.md +317 -0
- package/reference/codegen/scala-emitter.md +392 -0
- package/reference/codegen/scala-file.md +276 -0
- package/reference/codegen/sealed-trait.md +408 -0
- package/reference/codegen/type-definition.md +340 -0
- package/reference/codegen/type-ref.md +201 -0
- package/reference/combinators.md +347 -117
- package/reference/config.md +158 -0
- package/reference/context.md +4 -4
- package/reference/datastar.md +346 -0
- package/reference/docs.md +1461 -345
- package/reference/endpoint/auth-type.md +146 -0
- package/reference/endpoint/endpoint.md +297 -0
- package/reference/endpoint/http-codec.md +249 -0
- package/reference/endpoint/index.md +825 -0
- package/reference/endpoint/path-codec.md +237 -0
- package/reference/endpoint/route-pattern.md +196 -0
- package/reference/endpoint/route-tree.md +111 -0
- package/reference/endpoint/segment-codec.md +212 -0
- package/reference/html.md +1120 -0
- package/reference/htmx/attribute-values.md +359 -0
- package/reference/htmx/hx-encoding.md +111 -0
- package/reference/htmx/hx-params.md +204 -0
- package/reference/htmx/hx-swap.md +276 -0
- package/reference/htmx/hx-sync.md +251 -0
- package/reference/htmx/hx-target.md +314 -0
- package/reference/htmx/hx-trigger.md +457 -0
- package/reference/htmx/hx-url-update.md +239 -0
- package/reference/htmx/index.md +855 -0
- package/reference/http-model/index.md +47 -0
- package/reference/http-model/model.md +1481 -0
- package/reference/http-model/schema.md +747 -0
- package/reference/maybe.md +826 -0
- package/reference/media-type.md +2 -2
- package/reference/mux.mdx +823 -0
- package/reference/openapi.md +1351 -0
- package/reference/resource-management/defer-handle.md +1 -1
- package/reference/resource-management/resource.md +31 -2
- package/reference/resource-management/scope.md +28 -12
- package/reference/resource-management/wire.md +3 -7
- package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
- package/reference/ringbuffer/MpscDiagram.jsx +618 -0
- package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
- package/reference/ringbuffer/SpscDiagram.jsx +677 -0
- package/reference/ringbuffer/advanced.mdx +109 -0
- package/reference/ringbuffer/index.mdx +145 -0
- package/reference/ringbuffer/mpmc.mdx +151 -0
- package/reference/ringbuffer/mpsc.mdx +132 -0
- package/reference/ringbuffer/spmc.mdx +108 -0
- package/reference/ringbuffer/spsc.mdx +344 -0
- package/reference/{allows.md → schema/allows.md} +4 -4
- package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
- package/reference/{binding.md → schema/binding.md} +2 -3
- package/reference/schema/built-in-codecs/avro.md +451 -0
- package/reference/schema/built-in-codecs/bson.md +480 -0
- package/reference/schema/built-in-codecs/csv.md +564 -0
- package/reference/schema/built-in-codecs/index.md +77 -0
- package/reference/schema/built-in-codecs/json/index.md +295 -0
- package/reference/schema/built-in-codecs/json/json-config.md +217 -0
- package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
- package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
- package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
- package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
- package/reference/schema/built-in-codecs/messagepack.md +508 -0
- package/reference/schema/built-in-codecs/thrift.md +433 -0
- package/reference/schema/built-in-codecs/toon.md +1078 -0
- package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
- package/reference/schema/built-in-codecs/yaml.md +552 -0
- package/reference/{codec.md → schema/codec.md} +10 -10
- package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
- package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
- package/reference/schema/format.md +92 -0
- package/reference/schema/index.md +50 -0
- package/reference/schema/migration.md +297 -0
- package/reference/{modifier.md → schema/modifier.md} +58 -7
- package/reference/{optics.md → schema/optics.md} +2 -2
- package/reference/{patch.md → schema/patch.md} +1 -1
- package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
- package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
- package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
- package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
- package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
- package/reference/{schema.md → schema/schema.md} +12 -0
- package/reference/{structural-types.md → schema/structural-types.md} +1 -1
- package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
- package/reference/smithy.md +533 -0
- package/reference/sql/db-codec-deriver.md +71 -0
- package/reference/sql/db-codec.md +687 -0
- package/reference/sql/db-con.md +271 -0
- package/reference/sql/db-connection.md +153 -0
- package/reference/sql/db-param-writer.md +77 -0
- package/reference/sql/db-param.md +66 -0
- package/reference/sql/db-result-reader.md +146 -0
- package/reference/sql/db-tx.md +82 -0
- package/reference/sql/db-value.md +41 -0
- package/reference/sql/ddl.md +85 -0
- package/reference/sql/frag.md +254 -0
- package/reference/sql/index.md +341 -0
- package/reference/sql/repo.md +600 -0
- package/reference/sql/sql-dialect.md +73 -0
- package/reference/sql/sql-logger.md +62 -0
- package/reference/sql/sql-name-mapper.md +70 -0
- package/reference/sql/table-metadata.md +134 -0
- package/reference/sql/table.md +448 -0
- package/reference/sql/transactor-zio.md +399 -0
- package/reference/sql/transactor.md +353 -0
- package/reference/sql-zio.md +112 -0
- package/reference/streams/concurrent-operators.md +106 -0
- package/reference/streams/index.md +653 -0
- package/reference/streams/pipeline.md +718 -0
- package/reference/streams/reader.md +1284 -0
- package/reference/streams/scala-2-compatibility.md +55 -0
- package/reference/streams/sink.md +1426 -0
- package/reference/streams/stream.md +2526 -0
- package/reference/streams/writer.md +1045 -0
- package/reference/streams/zero-boxing.md +275 -0
- package/reference/telemetry.md +693 -0
- package/reference/typeid.md +5 -19
- package/sidebars.js +238 -43
- package/reference/formats.md +0 -694
- package/reference/http-model.md +0 -1716
- package/reference/streams.md +0 -989
- package/ringbuffer.md +0 -249
- /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
- /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
- /package/reference/{lazy.md → schema/lazy.md} +0 -0
- /package/reference/{reflect.md → schema/reflect.md} +0 -0
- /package/reference/{registers.md → schema/registers.md} +0 -0
- /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
- /package/reference/{syntax.md → schema/syntax.md} +0 -0
- /package/reference/{validation.md → schema/validation.md} +0 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: migration
|
|
3
|
+
title: "Migration"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Migration[A, B]` is ZIO Blocks Schema's typed API for evolving data from one schema version to another. It wraps a fully serializable [`DynamicMigration`](#dynamicmigration) core with typed source and target schemas, giving you a builder-based workflow for adding fields, dropping fields, renaming fields, changing types, migrating nested values, transforming collections and maps, and composing migrations.
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The migration system is split into three layers:
|
|
11
|
+
|
|
12
|
+
- **`Migration[A, B]`** — typed migration between source type `A` and target type `B`
|
|
13
|
+
- **`DynamicMigration`** — pure runtime representation built from serializable actions
|
|
14
|
+
- **`MigrationBuilder[A, B, Changeset]`** — macro-validated builder that accumulates actions and checks that the target shape is fully handled before `build`
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Migration[A, B]
|
|
18
|
+
├── sourceSchema: Schema[A]
|
|
19
|
+
├── targetSchema: Schema[B]
|
|
20
|
+
└── dynamicMigration: DynamicMigration
|
|
21
|
+
└── actions: Chunk[MigrationAction]
|
|
22
|
+
├── AddField / DropField / RenameField
|
|
23
|
+
├── TransformField / ChangeFieldType
|
|
24
|
+
├── MandateField / OptionalizeField
|
|
25
|
+
├── RenameCase / TransformCase
|
|
26
|
+
├── MigrateField
|
|
27
|
+
├── TransformElements / TransformKeys / TransformValues
|
|
28
|
+
└── Irreversible
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`Migration` is the user-facing entry point. `DynamicMigration` is the transport-friendly representation you can inspect, serialize, or apply dynamically.
|
|
32
|
+
|
|
33
|
+
## Creating a Migration
|
|
34
|
+
|
|
35
|
+
The normal entry point is `Migration.newBuilder[A, B]`:
|
|
36
|
+
|
|
37
|
+
```scala
|
|
38
|
+
import zio.blocks.schema._
|
|
39
|
+
import zio.blocks.schema.migration._
|
|
40
|
+
|
|
41
|
+
case class PersonV1(name: String)
|
|
42
|
+
case class PersonV2(name: String, age: Int)
|
|
43
|
+
|
|
44
|
+
object PersonV1 {
|
|
45
|
+
implicit val schema: Schema[PersonV1] = Schema.derived
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
object PersonV2 {
|
|
49
|
+
implicit val schema: Schema[PersonV2] = Schema.derived
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
val migration: Migration[PersonV1, PersonV2] =
|
|
53
|
+
Migration
|
|
54
|
+
.newBuilder[PersonV1, PersonV2]
|
|
55
|
+
.addField(_.age, SchemaExpr.literal(0))
|
|
56
|
+
.build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Applying the migration converts the source value to [`DynamicValue`](./dynamic-value.md), applies the dynamic actions, then converts the result back into the target type:
|
|
60
|
+
|
|
61
|
+
```scala
|
|
62
|
+
import zio.blocks.schema._
|
|
63
|
+
import zio.blocks.schema.migration._
|
|
64
|
+
|
|
65
|
+
case class PersonV1(name: String)
|
|
66
|
+
case class PersonV2(name: String, age: Int)
|
|
67
|
+
|
|
68
|
+
object PersonV1 {
|
|
69
|
+
implicit val schema: Schema[PersonV1] = Schema.derived
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
object PersonV2 {
|
|
73
|
+
implicit val schema: Schema[PersonV2] = Schema.derived
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
val migration = Migration
|
|
77
|
+
.newBuilder[PersonV1, PersonV2]
|
|
78
|
+
.addField(_.age, SchemaExpr.literal(0))
|
|
79
|
+
.build
|
|
80
|
+
|
|
81
|
+
val result = migration(PersonV1("Alice"))
|
|
82
|
+
// Right(PersonV2("Alice", 0))
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Core Operations
|
|
86
|
+
|
|
87
|
+
### `Migration#apply`
|
|
88
|
+
|
|
89
|
+
Transforms a typed value:
|
|
90
|
+
|
|
91
|
+
```scala
|
|
92
|
+
def apply(value: A): Either[SchemaError, B]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `Migration#reverse`
|
|
96
|
+
|
|
97
|
+
Returns the **structural reverse** of the migration:
|
|
98
|
+
|
|
99
|
+
```scala
|
|
100
|
+
def reverse: Migration[B, A]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
This is best-effort at runtime. Actions that lose information (for example `TransformField`, `ChangeFieldType`, `OptionalizeField`, or collection-wide transforms) reverse to `Irreversible`, which causes reverse execution to fail with a descriptive error instead of silently fabricating data.
|
|
104
|
+
|
|
105
|
+
### Composition
|
|
106
|
+
|
|
107
|
+
Migrations compose with `++` or `andThen`:
|
|
108
|
+
|
|
109
|
+
```scala
|
|
110
|
+
import zio.blocks.schema._
|
|
111
|
+
import zio.blocks.schema.migration._
|
|
112
|
+
|
|
113
|
+
case class PersonV1(name: String)
|
|
114
|
+
case class PersonV2(name: String, age: Int)
|
|
115
|
+
case class PersonV3(name: String, age: Int, city: String)
|
|
116
|
+
|
|
117
|
+
object PersonV1 { implicit val schema: Schema[PersonV1] = Schema.derived }
|
|
118
|
+
object PersonV2 { implicit val schema: Schema[PersonV2] = Schema.derived }
|
|
119
|
+
object PersonV3 { implicit val schema: Schema[PersonV3] = Schema.derived }
|
|
120
|
+
|
|
121
|
+
val migration = Migration
|
|
122
|
+
.newBuilder[PersonV1, PersonV2]
|
|
123
|
+
.addField(_.age, SchemaExpr.literal(0))
|
|
124
|
+
.build
|
|
125
|
+
|
|
126
|
+
val addCity = Migration
|
|
127
|
+
.newBuilder[PersonV2, PersonV3]
|
|
128
|
+
.addField(_.city, SchemaExpr.literal(""))
|
|
129
|
+
.build
|
|
130
|
+
|
|
131
|
+
val combined: Migration[PersonV1, PersonV3] = migration ++ addCity
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Builder Operations
|
|
135
|
+
|
|
136
|
+
`MigrationBuilder` uses selector expressions such as `_.field`, `_.nested.field`, `_.items.each`, and `_.data.eachValue`. The builder's third type parameter, `Changeset`, tracks which operations have already been applied so `build` can validate completeness.
|
|
137
|
+
|
|
138
|
+
### Record operations
|
|
139
|
+
|
|
140
|
+
- `addField(_.target, default)`
|
|
141
|
+
- `dropField(_.source, defaultForReverse)`
|
|
142
|
+
- `renameField(_.from, _.to)`
|
|
143
|
+
- `transformField(_.from, _.to, expr)`
|
|
144
|
+
- `mandateField(_.optionalSource, _.target, default)`
|
|
145
|
+
- `optionalizeField(_.source, _.optionalTarget)`
|
|
146
|
+
- `changeFieldType(_.source, expr)`
|
|
147
|
+
|
|
148
|
+
```scala
|
|
149
|
+
import zio.blocks.schema._
|
|
150
|
+
import zio.blocks.schema.migration._
|
|
151
|
+
|
|
152
|
+
case class UserV1(firstName: String, nickname: Option[String], age: Int)
|
|
153
|
+
case class UserV2(fullName: String, nickname: String, age: String)
|
|
154
|
+
|
|
155
|
+
object UserV1 {
|
|
156
|
+
implicit val schema: Schema[UserV1] = Schema.derived
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
object UserV2 {
|
|
160
|
+
implicit val schema: Schema[UserV2] = Schema.derived
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
val userMigration = Migration
|
|
164
|
+
.newBuilder[UserV1, UserV2]
|
|
165
|
+
.renameField(_.firstName, _.fullName)
|
|
166
|
+
.mandateField(_.nickname, _.nickname, SchemaExpr.literal("anonymous"))
|
|
167
|
+
.changeFieldType(_.age, SchemaExpr.literal("30"))
|
|
168
|
+
.build
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
`transformField` and `changeFieldType` evaluate their `SchemaExpr` against the currently focused field value, not the entire root record. In practice, that means the expression should be written in terms of the value being replaced.
|
|
172
|
+
|
|
173
|
+
### Nested migration with `migrateField`
|
|
174
|
+
|
|
175
|
+
When a nested value has its own migration, use `migrateField`:
|
|
176
|
+
|
|
177
|
+
```scala
|
|
178
|
+
import zio.blocks.schema._
|
|
179
|
+
import zio.blocks.schema.migration._
|
|
180
|
+
|
|
181
|
+
case class AddressV1(street: String, city: String)
|
|
182
|
+
case class AddressV2(street: String, city: String, zip: String)
|
|
183
|
+
case class PersonWithAddressV1(name: String, address: AddressV1)
|
|
184
|
+
case class PersonWithAddressV2(name: String, address: AddressV2)
|
|
185
|
+
|
|
186
|
+
object AddressV1 { implicit val schema: Schema[AddressV1] = Schema.derived }
|
|
187
|
+
object AddressV2 { implicit val schema: Schema[AddressV2] = Schema.derived }
|
|
188
|
+
object PersonWithAddressV1 { implicit val schema: Schema[PersonWithAddressV1] = Schema.derived }
|
|
189
|
+
object PersonWithAddressV2 { implicit val schema: Schema[PersonWithAddressV2] = Schema.derived }
|
|
190
|
+
|
|
191
|
+
val addressMigration = Migration
|
|
192
|
+
.newBuilder[AddressV1, AddressV2]
|
|
193
|
+
.addField(_.zip, SchemaExpr.literal("00000"))
|
|
194
|
+
.build
|
|
195
|
+
|
|
196
|
+
val personMigration = Migration
|
|
197
|
+
.newBuilder[PersonWithAddressV1, PersonWithAddressV2]
|
|
198
|
+
.migrateField(_.address, addressMigration)
|
|
199
|
+
.build
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
There is no `transformNested` builder method in the current API. Nested structural changes are expressed by building a migration for the nested type and applying it with `migrateField`.
|
|
203
|
+
|
|
204
|
+
### Enum and collection operations
|
|
205
|
+
|
|
206
|
+
- `renameCase(from, to)`
|
|
207
|
+
- `transformCase(caseName)(...)`
|
|
208
|
+
- `transformElements(_.items, expr)`
|
|
209
|
+
- `transformKeys(_.data, expr)`
|
|
210
|
+
- `transformValues(_.data, expr)`
|
|
211
|
+
|
|
212
|
+
Collection and map transforms are map-like operations. The `SchemaExpr` is evaluated once per matched value:
|
|
213
|
+
|
|
214
|
+
- `transformElements` evaluates against each collection element
|
|
215
|
+
- `transformKeys` evaluates against each map key
|
|
216
|
+
- `transformValues` evaluates against each map value
|
|
217
|
+
|
|
218
|
+
For example, `transformElements(_.scores, expr)` runs `expr` separately for every element in `scores`, and `transformValues(_.metadata, expr)` runs it separately for every map value.
|
|
219
|
+
|
|
220
|
+
## `build`
|
|
221
|
+
|
|
222
|
+
```scala
|
|
223
|
+
def build(using ev: MigrationComplete[A, B, Changeset]): Migration[A, B]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`build` runs macro validation. It checks that every field needed to transform `A` into `B` is either:
|
|
227
|
+
|
|
228
|
+
- auto-mapped by identical source/target structure, or
|
|
229
|
+
- explicitly handled by one of the builder operations
|
|
230
|
+
|
|
231
|
+
If the migration is incomplete, the code fails to compile.
|
|
232
|
+
|
|
233
|
+
This validated `build` step is the public completion path for `MigrationBuilder`. If you need unvalidated or dynamically assembled migrations, work at the `DynamicMigration` layer and wrap it with `Migration.fromDynamic`.
|
|
234
|
+
|
|
235
|
+
## `DynamicMigration`
|
|
236
|
+
|
|
237
|
+
`DynamicMigration` is the untyped runtime representation:
|
|
238
|
+
|
|
239
|
+
```scala
|
|
240
|
+
final case class DynamicMigration(actions: Chunk[MigrationAction]) {
|
|
241
|
+
def apply(value: DynamicValue): Either[SchemaError, DynamicValue]
|
|
242
|
+
def ++(that: DynamicMigration): DynamicMigration
|
|
243
|
+
def andThen(that: DynamicMigration): DynamicMigration
|
|
244
|
+
def reverse: DynamicMigration
|
|
245
|
+
def isEmpty: Boolean
|
|
246
|
+
def size: Int
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Use it when you need to inspect, serialize, or apply migrations without keeping the original Scala types around.
|
|
251
|
+
|
|
252
|
+
## `MigrationAction`
|
|
253
|
+
|
|
254
|
+
`MigrationAction` is the serializable ADT used by `DynamicMigration`.
|
|
255
|
+
|
|
256
|
+
| Action | Purpose | Reverse behavior |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| `AddField` | add a field with a default expression | `DropField` |
|
|
259
|
+
| `DropField` | remove a field | `AddField` using stored reverse default |
|
|
260
|
+
| `RenameField` | rename a record field | structural inverse rename |
|
|
261
|
+
| `TransformField` | replace a field by evaluating an expression against the current field value | `Irreversible` |
|
|
262
|
+
| `MandateField` | convert `Option[A]` to `A` with default for `None` | `OptionalizeField` |
|
|
263
|
+
| `OptionalizeField` | wrap a value in `Some` | `Irreversible` |
|
|
264
|
+
| `ChangeFieldType` | replace a field with a converted value computed from the current field value | `Irreversible` |
|
|
265
|
+
| `RenameCase` | rename enum case | inverse rename |
|
|
266
|
+
| `TransformCase` | run actions inside a specific case | reverse inner actions in reverse order |
|
|
267
|
+
| `MigrateField` | apply a nested `DynamicMigration` to a field | reverse nested migration |
|
|
268
|
+
| `TransformElements` | replace each collection element by evaluating the expression on that element | `Irreversible` |
|
|
269
|
+
| `TransformKeys` | replace each map key by evaluating the expression on that key | `Irreversible` |
|
|
270
|
+
| `TransformValues` | replace each map value by evaluating the expression on that value | `Irreversible` |
|
|
271
|
+
| `Irreversible` | explicit non-invertible sentinel | itself |
|
|
272
|
+
|
|
273
|
+
## Errors
|
|
274
|
+
|
|
275
|
+
Migration execution returns [`SchemaError`](./schema-error.md), with migration-specific kinds including:
|
|
276
|
+
|
|
277
|
+
- path not found
|
|
278
|
+
- type mismatch
|
|
279
|
+
- missing default
|
|
280
|
+
- transform failure
|
|
281
|
+
- field/case not found
|
|
282
|
+
- invalid value
|
|
283
|
+
- mandate failure
|
|
284
|
+
|
|
285
|
+
Errors carry a [`DynamicOptic`](./dynamic-optic.md) path so failures can be traced to the precise field or nested location that failed.
|
|
286
|
+
|
|
287
|
+
## Relationship to Other Schema-Evolution APIs
|
|
288
|
+
|
|
289
|
+
`Into` and `As` derive structural conversions automatically. `Migration` is lower-level and more explicit:
|
|
290
|
+
|
|
291
|
+
- use [`Into`](./schema-evolution/into.md) for one-way derived schema evolution
|
|
292
|
+
- use [`As`](./schema-evolution/as.md) for bidirectional derived evolution
|
|
293
|
+
- use `Migration` when you need explicit, inspectable, serializable transformation steps
|
|
294
|
+
|
|
295
|
+
:::tip
|
|
296
|
+
If you want a practical migration walkthrough rather than a reference, start with [`MigrationSpec`](https://github.com/zio/zio-blocks/blob/main/schema/shared/src/test/scala/zio/blocks/schema/migration/MigrationSpec.scala) and [`DynamicMigrationSpec`](https://github.com/zio/zio-blocks/blob/main/schema/shared/src/test/scala/zio/blocks/schema/migration/DynamicMigrationSpec.scala) in the repository until a dedicated migration guide lands.
|
|
297
|
+
:::
|
|
@@ -11,7 +11,7 @@ Modifiers are designed to be **pure data** values that can be serialized, making
|
|
|
11
11
|
sealed trait Modifier extends StaticAnnotation
|
|
12
12
|
object Modifier {
|
|
13
13
|
sealed trait Term extends Modifier
|
|
14
|
-
// ... term modifiers (transient, rename, alias, config) ...
|
|
14
|
+
// ... term modifiers (transient, encodeTransient, rename, alias, id, config) ...
|
|
15
15
|
sealed trait Reflect extends Modifier
|
|
16
16
|
// ... reflect modifiers (config) ...
|
|
17
17
|
}
|
|
@@ -119,12 +119,14 @@ Modifiers are organized into two main categories:
|
|
|
119
119
|
```
|
|
120
120
|
Modifier
|
|
121
121
|
├── Modifier.Term (annotates record fields and variant cases)
|
|
122
|
-
│ ├── transient()
|
|
123
|
-
│ ├──
|
|
124
|
-
│ ├──
|
|
125
|
-
│
|
|
122
|
+
│ ├── transient() : exclude from serialization
|
|
123
|
+
│ ├── encodeTransient() : exclude from encoding only
|
|
124
|
+
│ ├── rename(name) : change serialized name
|
|
125
|
+
│ ├── alias(name) : add alternative name
|
|
126
|
+
│ ├── id() : mark a primary-key field
|
|
127
|
+
│ └── config(key, val) : attach key-value metadata
|
|
126
128
|
└── Modifier.Reflect (annotates reflect values / types)
|
|
127
|
-
└── config(key, val)
|
|
129
|
+
└── config(key, val) : attach key-value metadata
|
|
128
130
|
```
|
|
129
131
|
|
|
130
132
|
As you can see, `config` is the only modifier that extends both `Term` and `Reflect`, allowing it to be used on both fields and types.
|
|
@@ -197,6 +199,23 @@ With this configuration:
|
|
|
197
199
|
|
|
198
200
|
This pattern is particularly useful when migrating data formats without breaking compatibility with existing data.
|
|
199
201
|
|
|
202
|
+
### id
|
|
203
|
+
|
|
204
|
+
The `id` modifier marks a record field as its primary key. It is interpreted by
|
|
205
|
+
`zio-blocks-sql` when deriving a `Repo[E, ID]`, and takes precedence over
|
|
206
|
+
name- and type-based ID discovery.
|
|
207
|
+
|
|
208
|
+
```scala
|
|
209
|
+
import zio.blocks.schema._
|
|
210
|
+
|
|
211
|
+
case class User(
|
|
212
|
+
email: String,
|
|
213
|
+
@Modifier.id() externalKey: Long
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Mark exactly one field with `@Modifier.id()` for a derived repository.
|
|
218
|
+
|
|
200
219
|
### config
|
|
201
220
|
|
|
202
221
|
The `config` modifier attaches arbitrary key-value metadata to a term (record fields or variant cases) or a type itself. The convention for keys is `<format>.<property>`, allowing format-specific configuration.
|
|
@@ -218,7 +237,7 @@ The `config` modifier extends both `Term` and `Reflect`, making it usable on bot
|
|
|
218
237
|
|
|
219
238
|
## Reflect Modifiers
|
|
220
239
|
|
|
221
|
-
Reflect modifiers annotate reflect values (types themselves).
|
|
240
|
+
Reflect modifiers annotate reflect values (types themselves). Alongside `config`, JSON derivation also understands dedicated reflect modifiers for discriminator selection, field naming, case naming, and rejecting extra fields.
|
|
222
241
|
|
|
223
242
|
### config
|
|
224
243
|
|
|
@@ -236,6 +255,33 @@ object Person {
|
|
|
236
255
|
}
|
|
237
256
|
```
|
|
238
257
|
|
|
258
|
+
### JSON reflect modifiers
|
|
259
|
+
|
|
260
|
+
These reflect modifiers are consumed by `JsonCodecDeriver` and let you keep JSON-specific settings next to the annotated type instead of passing them programmatically at every call site.
|
|
261
|
+
|
|
262
|
+
```scala
|
|
263
|
+
import zio.blocks.schema._
|
|
264
|
+
|
|
265
|
+
@Modifier.discriminator("type")
|
|
266
|
+
@Modifier.caseNaming("snake_case")
|
|
267
|
+
sealed trait Event
|
|
268
|
+
|
|
269
|
+
@Modifier.noExtraFields()
|
|
270
|
+
@Modifier.fieldNaming("snake_case")
|
|
271
|
+
case class UserProfile(
|
|
272
|
+
@Modifier.rename("userId") id: String,
|
|
273
|
+
@Modifier.encodeTransient() checksum: Int = 0,
|
|
274
|
+
displayName: String
|
|
275
|
+
)
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
- `Modifier.discriminator(name)` overrides the JSON discriminator field for sealed hierarchies.
|
|
279
|
+
- `Modifier.noExtraFields()` rejects unknown object fields during decoding.
|
|
280
|
+
- `Modifier.fieldNaming(strategy)` applies a naming strategy to direct record fields.
|
|
281
|
+
- `Modifier.caseNaming(strategy)` applies a naming strategy to variant case names.
|
|
282
|
+
|
|
283
|
+
For field-level JSON behavior, `Modifier.encodeTransient()` skips a field during encoding but still accepts it during decoding; like `transient`, it requires a default value when the field is neither optional nor a collection.
|
|
284
|
+
|
|
239
285
|
Or add multiple modifiers at once:
|
|
240
286
|
|
|
241
287
|
```scala
|
|
@@ -305,8 +351,13 @@ import zio.blocks.schema._
|
|
|
305
351
|
|
|
306
352
|
// Schema instances for individual modifiers
|
|
307
353
|
Schema[Modifier.transient]
|
|
354
|
+
Schema[Modifier.encodeTransient]
|
|
308
355
|
Schema[Modifier.rename]
|
|
309
356
|
Schema[Modifier.alias]
|
|
357
|
+
Schema[Modifier.discriminator]
|
|
358
|
+
Schema[Modifier.noExtraFields]
|
|
359
|
+
Schema[Modifier.fieldNaming]
|
|
360
|
+
Schema[Modifier.caseNaming]
|
|
310
361
|
Schema[Modifier.config]
|
|
311
362
|
|
|
312
363
|
// Schema instances for modifier traits
|
|
@@ -6,7 +6,7 @@ title: "Optics"
|
|
|
6
6
|
Optics are a fundamental feature of ZIO Blocks that enable type-safe, composable access and modification of nested data structures. What sets ZIO Blocks apart is its implementation of **reflective optics** — a novel construct that combines the operational capabilities of traditional optics with embedded structural metadata, enabling both data manipulation AND introspection.
|
|
7
7
|
|
|
8
8
|
:::tip
|
|
9
|
-
For a practical walkthrough of building query DSLs with optics, see the [Writing a Query DSL with Reified Optics](
|
|
9
|
+
For a practical walkthrough of building query DSLs with optics, see the [Writing a Query DSL with Reified Optics](../../guides/query-dsl-reified-optics.md) guide.
|
|
10
10
|
:::
|
|
11
11
|
|
|
12
12
|
## What Are Optics?
|
|
@@ -148,7 +148,7 @@ val updatedAddress: Address = Address.street.replace(address, "456 Elm St")
|
|
|
148
148
|
|
|
149
149
|
While manual lens construction gives you fine-grained control, ZIO Blocks provides macro-based derivation as the **preferred approach** for creating lenses.
|
|
150
150
|
|
|
151
|
-
The `optic` macro inside the `CompanionOptics` trait creates a lens using intuitive selector syntax that mirrors standard Scala field access
|
|
151
|
+
The `optic` macro inside the `CompanionOptics` trait creates a lens using intuitive selector syntax that mirrors standard Scala field access. In the rest of the schema docs you may also see the symbolic `$(_.field)` form; it is the same optic-construction API presented with symbolic syntax instead of the named `optic(_.field)` helper.
|
|
152
152
|
|
|
153
153
|
```scala
|
|
154
154
|
import zio.blocks.schema.optic
|
|
@@ -16,7 +16,7 @@ A `Patch[S]` represents a sequence of operations that transform a value of type
|
|
|
16
16
|
- **Schema Evolution** — Patches work with the schema system, adapting as data structures evolve
|
|
17
17
|
|
|
18
18
|
:::note
|
|
19
|
-
For **untyped JSON patching** without a schema, use [`JsonPatch`](./json-patch.md) instead. `JsonPatch` is optimized for diff-and-apply workflows on raw JSON values and provides compact delta representations without requiring typed optics.
|
|
19
|
+
For **untyped JSON patching** without a schema, use [`JsonPatch`](./built-in-codecs/json/json-patch.md) instead. `JsonPatch` is optimized for diff-and-apply workflows on raw JSON values and provides compact delta representations without requiring typed optics.
|
|
20
20
|
:::
|
|
21
21
|
|
|
22
22
|
```scala
|