@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.
Files changed (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,687 @@
1
+ ---
2
+ id: db-codec
3
+ title: "DbCodec"
4
+ description: "Reference for DbCodec[A], the foundational bidirectional codec between Scala values and database columns in the sql module."
5
+ keywords:
6
+ - "DbCodec Schema derivation"
7
+ - "Bidirectional Column Codec"
8
+ - "JDBC Row Mapping"
9
+ - "DbResultReader DbParamWriter"
10
+ - "JSONB Column Encoding"
11
+ - "Opaque Type Codec"
12
+ - "SQL Module Codec"
13
+ ---
14
+
15
+ `DbCodec[A]` is a bidirectional codec between a Scala value of type `A` and one or more database columns. Every read-side operation — fetching rows from a result set — and every write-side operation — binding parameters to a prepared statement — flows through a `DbCodec`. It is the foundational type in the `sql` module: [`Frag`](./frag.md) uses it to decode query results, [`Table`](./table.md) carries it as column metadata, and [`Repo`](./repo.md) relies on it to map entity rows to and from the database.
16
+
17
+ Key properties:
18
+ - **Bidirectional** — the same type handles both encoding (write) and decoding (read), keeping the two directions in sync.
19
+ - **Multi-column** — a single codec spans any number of database columns; a case class codec produces one column per field.
20
+ - **Label-based and positional** — `readValue` supports order-independent decoding by column label or fast 1-based positional access per the JDBC convention.
21
+ - **Schema-driven** — `DbCodec.derived` and `DbCodecDeriver` produce codecs automatically from a `Schema[A]` at compile time, with no runtime reflection.
22
+ - **Null-safe** — `Option[A]` and `Maybe[A]` codecs handle SQL `NULL` transparently; non-optional types throw `IllegalStateException` on unexpected `NULL`, surfacing schema mismatches immediately rather than silently coercing.
23
+
24
+ ## Core API
25
+
26
+ ```scala
27
+ import zio.blocks.sql.{DbResultReader, DbParamWriter, DbValue}
28
+ import zio.blocks.schema.derive.DerivationBuilder
29
+
30
+ trait DbCodec[A] {
31
+ // Column inspection
32
+ def columns: IndexedSeq[String]
33
+ def columnCount: Int
34
+
35
+ // Read operations
36
+ def readValue(reader: DbResultReader, startIndex: Int): A
37
+ def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): A
38
+
39
+ // Write operations
40
+ def writeValue(writer: DbParamWriter, startIndex: Int, value: A): Unit
41
+ def toDbValues(value: A): IndexedSeq[DbValue]
42
+
43
+ // Transformation
44
+ def transform[B](read: A => B)(write: B => A): DbCodec[B]
45
+ }
46
+
47
+ object DbCodec {
48
+ // Automatic derivation from Schema[A]
49
+ inline given derived[A]: DbCodec[A]
50
+ inline given derivedOpaque[A]: DbCodec[A]
51
+
52
+ // Customize derivation
53
+ inline def builder[A]: DerivationBuilder[DbCodec, A]
54
+ inline def derivedWith[A](
55
+ configure: DerivationBuilder[DbCodec, A] => DerivationBuilder[DbCodec, A]
56
+ ): DbCodec[A]
57
+
58
+ // Retrieval
59
+ def apply[A](implicit codec: DbCodec[A]): DbCodec[A]
60
+
61
+ // Built-in instances
62
+ // given instances for: Int, Long, String, Boolean, Double, Float, Short, Byte,
63
+ // BigDecimal, Instant, UUID, ...
64
+ }
65
+ ```
66
+
67
+ Codecs for JSON/JSONB columns, type conversions, and specialized encoding strategies are also available through additional companion object methods and instances.
68
+
69
+ ## Usage
70
+
71
+ The following example shows the core lifecycle of a `DbCodec`: deriving one automatically, inspecting its column metadata, encoding a value, handling nullable columns, and adapting the codec to a newtype:
72
+
73
+ ```scala
74
+ import zio.blocks.sql._
75
+ import zio.blocks.schema.Schema
76
+
77
+ // Derive a codec automatically — field names map to snake_case columns by default
78
+ case class User(id: Int, name: String, email: Option[String]) derives DbCodec
79
+
80
+ val codec = DbCodec[User]
81
+ // codec: DbCodec[User] = zio.blocks.sql.DbCodecDeriver$$anon$20@55869d8
82
+
83
+ codec.columns
84
+ // res1: IndexedSeq[String] = Vector("id", "name", "email")
85
+ codec.columnCount
86
+ // res2: Int = 3
87
+
88
+ // Encode a value for use as SQL parameters
89
+ val params = codec.toDbValues(User(1, "Alice", Some("alice@example.com")))
90
+ // params: IndexedSeq[DbValue] = Vector(
91
+ // DbInt(1),
92
+ // DbString("Alice"),
93
+ // DbString("alice@example.com")
94
+ // )
95
+
96
+ // None encodes as SQL NULL
97
+ val nullParams = codec.toDbValues(User(2, "Bob", None))
98
+ // nullParams: IndexedSeq[DbValue] = Vector(DbInt(2), DbString("Bob"), DbNull)
99
+
100
+ // Adapt any codec to a newtype with transform — no full Schema needed
101
+ case class UserId(value: Int)
102
+ val userIdCodec: DbCodec[UserId] = DbCodec[Int].transform(UserId(_))(_.value)
103
+ // userIdCodec: DbCodec[UserId] = zio.blocks.sql.DbCodec$$anon$1@2f13d28a
104
+
105
+ userIdCodec.columns
106
+ // res3: IndexedSeq[String] = Vector("value")
107
+ userIdCodec.toDbValues(UserId(42))
108
+ // res4: IndexedSeq[DbValue] = Vector(DbInt(42))
109
+ ```
110
+
111
+ ## Construction / Creating Instances
112
+
113
+ We can obtain a `DbCodec[A]` in several ways: automatic schema derivation (the most common path), structured derivation with field-level overrides, JSONB wrapping for complex types, opaque-type support, and manual composition.
114
+
115
+ ### `DbCodec.derived` — Automatic schema-driven derivation
116
+
117
+ `DbCodec.derived` is a Scala 3 `inline given` that produces a `DbCodec[A]` by internally deriving `Schema[A]` and running it through `DbCodecDeriver`. Because it derives `Schema` itself, no explicit `Schema` needs to be in scope. It also enables the Scala 3 `derives` clause.
118
+
119
+ ```scala
120
+ object DbCodec {
121
+ inline given derived[A]: DbCodec[A]
122
+ }
123
+ ```
124
+
125
+ The two most common spellings are the `derives` clause on the case class and explicit summoning:
126
+
127
+ ```scala
128
+ import zio.blocks.sql._
129
+
130
+ // Option 1: derives clause
131
+ case class Product(sku: String, price: BigDecimal, inStock: Boolean) derives DbCodec
132
+
133
+ // Option 2: explicit given
134
+ case class Category(id: Int, name: String)
135
+ given DbCodec[Category] = DbCodec.derived
136
+
137
+ // Columns follow the default SqlNameMapper (SnakeCase):
138
+ // Product → "sku", "price", "in_stock"
139
+ // Category → "id", "name"
140
+ ```
141
+
142
+ `DbCodec.derived` delegates to `DbCodecDeriver`, which handles primitives, case classes, enums, sealed traits, `Option`/`Maybe` fields, and JSONB-encoded complex fields. Enum and sealed-trait variants serialize to their name as a `String` column unless annotated with `@Modifier.rename`.
143
+
144
+ :::tip
145
+ For case classes with fields that need custom codecs or name overrides, prefer `DbCodec.derivedWith` so you can supply those overrides through the `DerivationBuilder` API.
146
+ :::
147
+
148
+ ### `DbCodec.derivedWith` — Derivation with field-level overrides
149
+
150
+ `DbCodec.derivedWith` derives a `DbCodec[A]` and applies caller-supplied overrides before finalizing the codec. This is the right choice when a specific field needs a custom codec — for example, a JSON-encoded value type or a field stored in a non-default format.
151
+
152
+ ```scala
153
+ object DbCodec {
154
+ inline def derivedWith[A](
155
+ configure: DerivationBuilder[DbCodec, A] => DerivationBuilder[DbCodec, A]
156
+ ): DbCodec[A]
157
+ }
158
+ ```
159
+
160
+ The configure function receives a `DerivationBuilder[DbCodec, A]` and returns a modified one. We call `DerivationBuilder#instance` to attach a custom `DbCodec` for a specific field, identified by the enclosing type's `TypeId` and the field name:
161
+
162
+ ```scala
163
+ import zio.blocks.sql._
164
+ import zio.blocks.schema.Schema
165
+ import zio.blocks.typeid.TypeId
166
+
167
+ case class Tags(values: List[String])
168
+ object Tags { implicit val schema: Schema[Tags] = Schema.derived }
169
+
170
+ case class Product(id: Int, tags: Tags)
171
+ object Product { implicit val schema: Schema[Product] = Schema.derived }
172
+
173
+ // tags is stored as a JSON string in the "tags" column
174
+ val tagsCodec: DbCodec[Tags] =
175
+ DbCodec[String].transform(json => Tags(json.split(",").toList))(_.values.mkString(","))
176
+ // tagsCodec: DbCodec[Tags] = zio.blocks.sql.DbCodec$$anon$1@3592645d
177
+
178
+ val productCodec: DbCodec[Product] =
179
+ DbCodec.derivedWith[Product](
180
+ _.instance(TypeId.of[Product], "tags", tagsCodec)
181
+ )
182
+ // productCodec: DbCodec[Product] = zio.blocks.sql.DbCodecDeriver$$anon$20@73120731
183
+
184
+ productCodec.columns
185
+ // res7: IndexedSeq[String] = Vector("id", "tags")
186
+ productCodec.columnCount
187
+ // res8: Int = 2
188
+ ```
189
+
190
+ ### `DbCodec.jsonb` — JSONB column codec
191
+
192
+ `DbCodec.jsonb` creates a `DbCodec[A]` that stores and retrieves a value of type `A` as a JSON string in a single database column. Two overloads are available: one using an implicit `JsonSchemaCodec[A]` for the encode/decode pair, and one accepting explicit functions.
193
+
194
+ ```scala
195
+ object DbCodec {
196
+ def jsonb[A](using jsonCodec: JsonSchemaCodec[A]): DbCodec[A]
197
+ def jsonb[A](encode: A => String, decode: String => A): DbCodec[A]
198
+ }
199
+ ```
200
+
201
+ The first overload requires a `JsonSchemaCodec[A]` (aliased from `zio.blocks.schema.json.JsonCodec`) in implicit scope:
202
+
203
+ ```scala
204
+ import zio.blocks.sql._
205
+ import zio.blocks.schema.Schema
206
+ import zio.blocks.schema.json.{JsonCodec => JsonSchemaCodec, JsonCodecDeriver}
207
+
208
+ case class Address(street: String, city: String)
209
+ object Address {
210
+ implicit val schema: Schema[Address] = Schema.derived
211
+ implicit val jsonCodec: JsonSchemaCodec[Address] = schema.deriving(JsonCodecDeriver).derive
212
+ }
213
+
214
+ // Address is stored as a JSON string in a single TEXT/JSONB column
215
+ val codec: DbCodec[Address] = DbCodec.jsonb[Address]
216
+ // codec: DbCodec[Address] = zio.blocks.sql.DbCodec$$anon$1@68d986d1
217
+
218
+ codec.columns
219
+ // res10: IndexedSeq[String] = Vector("value")
220
+ codec.toDbValues(Address("Main St", "NYC"))
221
+ // res11: IndexedSeq[DbValue] = Vector(
222
+ // DbString("{\"street\":\"Main St\",\"city\":\"NYC\"}")
223
+ // )
224
+ ```
225
+
226
+ Use the two-argument overload when you supply custom encode/decode logic instead of relying on `JsonSchemaCodec`:
227
+
228
+ ```scala
229
+ import zio.blocks.sql._
230
+
231
+ case class Point(x: Double, y: Double)
232
+
233
+ // Custom JSON encoding using a hand-rolled format
234
+ val pointCodec: DbCodec[Point] = DbCodec.jsonb[Point](
235
+ p => s"${p.x},${p.y}",
236
+ s => { val parts = s.split(","); Point(parts(0).toDouble, parts(1).toDouble) }
237
+ )
238
+ // pointCodec: DbCodec[Point] = zio.blocks.sql.DbCodec$$anon$1@1144ae3c
239
+
240
+ pointCodec.toDbValues(Point(1.0, 2.0))
241
+ // res12: IndexedSeq[DbValue] = Vector(DbString("1.0,2.0"))
242
+ ```
243
+
244
+ ### `DbCodec.jsonbOption` — Nullable JSONB column codec
245
+
246
+ `DbCodec.jsonbOption` creates a `DbCodec[Option[A]]` that stores `Some(a)` as a JSON string and `None` as SQL `NULL`. Like `jsonb`, it has an implicit `JsonSchemaCodec[A]` overload and a two-argument overload:
247
+
248
+ ```scala
249
+ object DbCodec {
250
+ def jsonbOption[A](using jsonCodec: JsonSchemaCodec[A]): DbCodec[Option[A]]
251
+ def jsonbOption[A](encode: A => String, decode: String => A): DbCodec[Option[A]]
252
+ }
253
+ ```
254
+
255
+ The codec delegates to `DbCodec[Option[String]]` and applies the JSON encode/decode on the inner `String`, so `NULL` detection uses the underlying `Option[String]` codec's standard null handling:
256
+
257
+ ```scala
258
+ import zio.blocks.sql._
259
+ import zio.blocks.schema.json.{JsonCodec => JsonSchemaCodec}
260
+
261
+ // Assume JsonSchemaCodec[Address] is in scope from the previous example
262
+ val nullableCodec: DbCodec[Option[Address]] = DbCodec.jsonbOption[Address]
263
+ // nullableCodec: DbCodec[Option[Address]] = zio.blocks.sql.DbCodec$$anon$1@4c7b464f
264
+
265
+ nullableCodec.toDbValues(Some(Address("Elm St", "LA")))
266
+ // res13: IndexedSeq[DbValue] = Vector(
267
+ // DbString("{\"street\":\"Elm St\",\"city\":\"LA\"}")
268
+ // )
269
+
270
+ nullableCodec.toDbValues(None)
271
+ // res14: IndexedSeq[DbValue] = Vector(DbNull)
272
+ ```
273
+
274
+ ### `DbCodec.derivedOpaque` — Opaque type derivation
275
+
276
+ `DbCodec.derivedOpaque` is a lower-priority `inline given` that produces a `DbCodec[A]` for Scala 3 opaque types by reusing the codec of the underlying type. The compiler selects it automatically when `A` is an opaque type and no explicit `DbCodec[A]` is in scope.
277
+
278
+ ```scala
279
+ object DbCodec {
280
+ inline given derivedOpaque[A]: DbCodec[A]
281
+ }
282
+ ```
283
+
284
+ For the decode direction the opaque type's companion `apply` is called. For the encode direction, if the opaque type is declared as a subtype of its underlying type (`opaque type T <: U = U`), the value is used directly; otherwise the companion must expose an `unwrap` method:
285
+
286
+ ```scala
287
+ import zio.blocks.sql._
288
+
289
+ opaque type ProductId <: String = String
290
+ object ProductId {
291
+ def apply(value: String): ProductId = value
292
+ }
293
+
294
+ // DbCodec[ProductId] is resolved automatically — no explicit given needed
295
+ val codec = DbCodec[ProductId]
296
+ // codec: DbCodec[ProductId] = zio.blocks.sql.DbCodec$$anon$6@3261707e
297
+ codec.columns
298
+ // res16: IndexedSeq[String] = Vector("value")
299
+ ```
300
+
301
+ :::caution
302
+ `DbCodec.derivedOpaque` is a Scala 3-only macro. The `sql` module requires Scala 3.
303
+ :::
304
+
305
+ ### `DbCodec.dbCodecFromAs` — Codec derivation via `As` conversion
306
+
307
+ `DbCodec.dbCodecFromAs` is a `given` that derives `DbCodec[B]` from `DbCodec[A]` and an `As[A, B]` conversion. It enables opaque types and newtype wrappers to receive a `DbCodec` automatically when their underlying type already has one and an `As[A, B]` instance is provided:
308
+
309
+ ```scala
310
+ object DbCodec {
311
+ given dbCodecFromAs[A, B](using conv: As[A, B], base: DbCodec[A]): DbCodec[B]
312
+ }
313
+ ```
314
+
315
+ `As[A, B]` (from `zio.blocks.schema`) represents a validated conversion from `A` to `B` and from `B` back to `A`. The derived codec applies `As#into` on decode and `As#from` on encode; if either conversion returns a `Left`, an `IllegalStateException` is thrown at runtime:
316
+
317
+ ```scala
318
+ import zio.blocks.sql._
319
+ import zio.blocks.schema.As
320
+
321
+ // Suppose As[String, EmailAddress] is defined and EmailAddress wraps String
322
+ // DbCodec[EmailAddress] is then resolved automatically — no explicit given needed
323
+ // val emailCodec = DbCodec[EmailAddress]
324
+ ```
325
+
326
+ For types without an `As` instance, use `DbCodec[A].transform` instead.
327
+
328
+ ### `DbCodec.apply` — Summoning an instance
329
+
330
+ `DbCodec.apply` summons an implicitly available `DbCodec[A]` from the current scope. It is the standard way to access a codec without writing `implicitly` or `summon`:
331
+
332
+ ```scala
333
+ object DbCodec {
334
+ def apply[A](implicit codec: DbCodec[A]): DbCodec[A]
335
+ }
336
+ ```
337
+
338
+ We use `DbCodec.apply` whenever we need a codec value without knowing its derivation path:
339
+
340
+ ```scala
341
+ import zio.blocks.sql._
342
+
343
+ case class Order(id: Long, status: String) derives DbCodec
344
+
345
+ // Summon the derived codec
346
+ val codec: DbCodec[Order] = DbCodec[Order]
347
+ // codec: DbCodec[Order] = zio.blocks.sql.DbCodecDeriver$$anon$20@678700e1
348
+ codec.columns
349
+ // res19: IndexedSeq[String] = Vector("id", "status")
350
+ ```
351
+
352
+ ### `DbCodec.builder` — Derivation builder
353
+
354
+ `DbCodec.builder[A]` returns a `DerivationBuilder[DbCodec, A]` pre-seeded with the derived schema for `A`. Use it when you need to attach multiple field-level overrides before calling `.derive` to finalize the codec, giving you full control over the build process:
355
+
356
+ ```scala
357
+ object DbCodec {
358
+ inline def builder[A]: DerivationBuilder[DbCodec, A]
359
+ }
360
+ ```
361
+
362
+ `DerivationBuilder` exposes `instance` to attach custom codecs for individual fields and `derive` to produce the final codec. `DbCodec.derivedWith` is a one-liner wrapper around `builder`:
363
+
364
+ ```scala
365
+ import zio.blocks.sql._
366
+ import zio.blocks.typeid.TypeId
367
+
368
+ case class Order(id: Long, tags: List[String], metadata: Map[String, String])
369
+
370
+ // Build manually — equivalent to derivedWith but explicit
371
+ val codec: DbCodec[Order] =
372
+ DbCodec
373
+ .builder[Order]
374
+ .instance(TypeId.of[Order], "tags", DbCodec[String].transform(_.split(",").toList)(_.mkString(",")))
375
+ .derive
376
+ ```
377
+
378
+ ## Predefined Instances
379
+
380
+ `DbCodec` provides `given` instances for all primitive and common JVM types. Each occupies a single column named `"value"`:
381
+
382
+ | Scala Type | Given Name | `DbValue` variant | Notes |
383
+ |------------------------|--------------------|------------------------------|----------------------------------------------------|
384
+ | `Int` | `intCodec` | `DbValue.DbInt` | |
385
+ | `Long` | `longCodec` | `DbValue.DbLong` | |
386
+ | `String` | `stringCodec` | `DbValue.DbString` | |
387
+ | `Boolean` | `booleanCodec` | `DbValue.DbBoolean` | |
388
+ | `Double` | `doubleCodec` | `DbValue.DbDouble` | |
389
+ | `Float` | `floatCodec` | `DbValue.DbFloat` | |
390
+ | `Short` | `shortCodec` | `DbValue.DbShort` | |
391
+ | `Byte` | `byteCodec` | `DbValue.DbByte` | |
392
+ | `BigDecimal` | `bigDecimalCodec` | `DbValue.DbBigDecimal` | Throws on SQL `NULL`; use `Option[BigDecimal]` for nullable columns. |
393
+ | `java.time.Instant` | `instantCodec` | `DbValue.DbInstant` | |
394
+ | `Option[A]` | `optionCodec` | inner or `DbValue.DbNull` | Requires a `DbCodec[A]`; single-column inner only. |
395
+ | `Maybe[A]` | `maybeCodec` | inner or `DbValue.DbNull` | Requires a `DbCodec[A]`; single-column inner only. |
396
+
397
+ All primitive codecs set their single column name to `"value"`. When a primitive codec is used as part of a record derivation, `DbCodecDeriver` replaces the column name with the field name (after applying the `SqlNameMapper`).
398
+
399
+ ## Core Operations
400
+
401
+ The five abstract and one final method on `DbCodec` divide into four operational groups: column metadata inspection, decoding from a result set, encoding to prepared-statement parameters, and transformation.
402
+
403
+ ### Column Metadata
404
+
405
+ The column metadata methods expose the names and count of columns a codec spans. They are used by `Table`, `Repo`, and `Frag` to build SQL `SELECT`, `INSERT`, and `UPDATE` clauses without any per-call string assembly.
406
+
407
+ #### `columns` — Ordered column names
408
+
409
+ `DbCodec#columns` returns the ordered `IndexedSeq[String]` of database column names for this codec. The sequence matches the order in which `readValue` and `writeValue` consume and produce values.
410
+
411
+ ```scala
412
+ trait DbCodec[A] {
413
+ def columns: IndexedSeq[String]
414
+ }
415
+ ```
416
+
417
+ For a case class codec produced by `DbCodec.derived`, each field maps to one column name after the `SqlNameMapper` (default: `SnakeCase`). Annotating a field with `@Modifier.rename("custom_name")` overrides the mapped name:
418
+
419
+ ```scala
420
+ import zio.blocks.sql._
421
+ import zio.blocks.schema.{Schema, Modifier}
422
+
423
+ case class BlogPost(
424
+ @Modifier.rename("post_id") id: Int,
425
+ authorName: String
426
+ ) derives DbCodec
427
+
428
+ DbCodec[BlogPost].columns
429
+ // res22: IndexedSeq[String] = Vector("post_id", "author_name")
430
+ ```
431
+
432
+ #### `columnCount` — Number of columns
433
+
434
+ `DbCodec#columnCount` returns the number of columns this codec spans. It is derived from `columns.size` and provided as a concrete method:
435
+
436
+ ```scala
437
+ trait DbCodec[A] {
438
+ def columnCount: Int = columns.size
439
+ }
440
+ ```
441
+
442
+ We use `columnCount` to validate multi-column usage and to calculate offsets when composing codecs. For all primitive codecs, `columnCount` is `1`. For a case class, it equals the number of non-transient fields (fields annotated with `@Modifier.transient()` are excluded):
443
+
444
+ ```scala
445
+ import zio.blocks.sql._
446
+ import zio.blocks.schema.{Schema, Modifier}
447
+
448
+ case class Event(name: String, @Modifier.transient() internalFlag: Boolean = false) derives DbCodec
449
+
450
+ DbCodec[Event].columnCount // "internalFlag" is excluded
451
+ // res24: Int = 1
452
+ ```
453
+
454
+ ### Reading / Decoding
455
+
456
+ The two `readValue` overloads decode a Scala value from a `DbResultReader`, which abstracts over a JDBC `ResultSet`. Query execution in `Frag` prefers the label-based overload so that result column order can differ from codec column order.
457
+
458
+ #### `readValue` — Positional read
459
+
460
+ `DbCodec#readValue(reader, startIndex)` reads a value of type `A` from the result reader starting at the given 1-based column index. For a multi-column codec, it reads `columnCount` consecutive columns beginning at `startIndex`. Internally this overload delegates to the label-based overload by calling `DbResultReader#columnLabel` for each offset.
461
+
462
+ ```scala
463
+ trait DbCodec[A] {
464
+ def readValue(reader: DbResultReader, startIndex: Int): A
465
+ }
466
+ ```
467
+
468
+ The default implementation converts positional access to label-based access automatically, so implementing only the label-based overload is sufficient when writing a custom `DbCodec`:
469
+
470
+ ```scala
471
+ import zio.blocks.sql._
472
+
473
+ // For illustration: a custom single-column String codec
474
+ val uppercaseCodec: DbCodec[String] = new DbCodec[String] {
475
+ val columns: IndexedSeq[String] = IndexedSeq("value")
476
+ def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): String =
477
+ reader.getString(columnLabels.head).toUpperCase
478
+ def writeValue(writer: DbParamWriter, startIndex: Int, value: String): Unit =
479
+ writer.setString(startIndex, value)
480
+ def toDbValues(value: String): IndexedSeq[DbValue] =
481
+ IndexedSeq(DbValue.DbString(value))
482
+ }
483
+
484
+ // positional overload works automatically
485
+ // uppercaseCodec.readValue(reader, 1) → delegates to label-based via columnLabel(1)
486
+ ```
487
+
488
+ :::caution
489
+ `startIndex` is 1-based per the JDBC convention. Passing `0` will cause an out-of-bounds error in the underlying `ResultSet`.
490
+ :::
491
+
492
+ #### `readValue` — Label-based read
493
+
494
+ `DbCodec#readValue(reader, columnLabels)` reads a value of type `A` by looking up each column by label, allowing the result set's column order to differ from the codec's column order. `Frag#query` always calls this overload, passing the labels derived from the query's `SELECT` list.
495
+
496
+ ```scala
497
+ trait DbCodec[A] {
498
+ def readValue(reader: DbResultReader, columnLabels: IndexedSeq[String]): A
499
+ }
500
+ ```
501
+
502
+ The caller must supply exactly `columnCount` labels in the logical order matching the codec's `columns` sequence. In practice, `Frag` constructs this sequence automatically from the query result metadata:
503
+
504
+ ```scala
505
+ import zio.blocks.sql._
506
+
507
+ case class User(id: Int, name: String) derives DbCodec
508
+
509
+ val codec = DbCodec[User]
510
+
511
+ // Calling with explicit labels — useful for custom result processing
512
+ // codec.readValue(reader, IndexedSeq("id", "name")) → User(...)
513
+ ```
514
+
515
+ ### Writing / Encoding
516
+
517
+ The two encoding methods convert a Scala value into database parameters: `writeValue` binds values directly to a `DbParamWriter` (a prepared statement), while `toDbValues` converts them to the typed `DbValue` ADT for inspection, testing, and logging.
518
+
519
+ #### `writeValue` — Bind to a prepared statement
520
+
521
+ `DbCodec#writeValue` writes a value of type `A` to a `DbParamWriter` starting at the given 1-based parameter index. For a multi-column codec, it writes exactly `columnCount` consecutive parameters beginning at `startIndex`.
522
+
523
+ ```scala
524
+ trait DbCodec[A] {
525
+ def writeValue(writer: DbParamWriter, startIndex: Int, value: A): Unit
526
+ }
527
+ ```
528
+
529
+ `Frag` and `Repo` call this method to bind parameters when executing `INSERT` and `UPDATE` statements. For `None` / `Maybe.absent`, the codec calls `DbParamWriter#setNull` with `java.sql.Types.NULL`:
530
+
531
+ ```scala
532
+ import zio.blocks.sql._
533
+
534
+ case class Point(x: Double, y: Double) derives DbCodec
535
+
536
+ val codec = DbCodec[Point]
537
+ // codec.writeValue(writer, 1, Point(3.0, 4.0))
538
+ // → writer.setDouble(1, 3.0); writer.setDouble(2, 4.0)
539
+ ```
540
+
541
+ :::caution
542
+ `startIndex` is 1-based. The codec writes exactly `columnCount` parameters, so if you compose two codecs at offsets `i` and `i + codec.columnCount`, the second start index must be adjusted accordingly.
543
+ :::
544
+
545
+ #### `toDbValues` — Convert to `DbValue` representation
546
+
547
+ `DbCodec#toDbValues` converts a value of type `A` into an `IndexedSeq[DbValue]`, one element per column. The result is parallel to `columns` — `toDbValues(v)(i)` corresponds to `columns(i)`.
548
+
549
+ ```scala
550
+ trait DbCodec[A] {
551
+ def toDbValues(value: A): IndexedSeq[DbValue]
552
+ }
553
+ ```
554
+
555
+ `toDbValues` is used by `Frag` and `Repo` to inspect or log parameters before binding, and in tests to assert encoding behavior without a real database connection:
556
+
557
+ ```scala
558
+ import zio.blocks.sql._
559
+
560
+ case class Item(id: Int, name: String, price: Option[BigDecimal]) derives DbCodec
561
+
562
+ val codec = DbCodec[Item]
563
+ // codec: DbCodec[Item] = zio.blocks.sql.DbCodecDeriver$$anon$20@1ffebf02
564
+
565
+ codec.toDbValues(Item(1, "Widget", Some(BigDecimal("9.99"))))
566
+ // res29: IndexedSeq[DbValue] = Vector(
567
+ // DbInt(1),
568
+ // DbString("Widget"),
569
+ // DbBigDecimal(9.99)
570
+ // )
571
+
572
+ codec.toDbValues(Item(2, "Gadget", None))
573
+ // res30: IndexedSeq[DbValue] = Vector(DbInt(2), DbString("Gadget"), DbNull)
574
+ ```
575
+
576
+ ### Transformations
577
+
578
+ #### `transform` — Map a codec to a new type
579
+
580
+ `DbCodec#transform` returns a new `DbCodec[B]` by mapping the read direction with `read: A => B` and the write direction with `write: B => A`. The resulting codec shares the same `columns` as the original and is the lightest way to create a codec for a newtype or value wrapper without defining a full `Schema`:
581
+
582
+ ```scala
583
+ trait DbCodec[A] {
584
+ final def transform[B](read: A => B)(write: B => A): DbCodec[B]
585
+ }
586
+ ```
587
+
588
+ Both `read` and `write` must be total functions; any exception they throw propagates to the caller. The transformed codec delegates all column metadata and read/write operations to the inner codec after applying the conversions:
589
+
590
+ ```scala
591
+ import zio.blocks.sql._
592
+
593
+ case class ProductId(value: String)
594
+
595
+ // Adapt the String codec to ProductId without a Schema
596
+ val productIdCodec: DbCodec[ProductId] =
597
+ DbCodec[String].transform(ProductId(_))(_.value)
598
+ // productIdCodec: DbCodec[ProductId] = zio.blocks.sql.DbCodec$$anon$1@95d8a43
599
+
600
+ productIdCodec.columns
601
+ // res32: IndexedSeq[String] = Vector("value")
602
+ productIdCodec.toDbValues(ProductId("abc-1"))
603
+ // res33: IndexedSeq[DbValue] = Vector(DbString("abc-1"))
604
+ ```
605
+
606
+ `DbCodec#transform` is also the engine behind `DbCodec.jsonb`, `DbCodec.jsonbOption`, and `DbCodec.dbCodecFromAs` — each of those constructors builds on top of an existing primitive or composite codec and applies `transform` to attach custom encode/decode logic.
607
+
608
+ ## Supporting Types
609
+
610
+ The two interfaces that `DbCodec` depends on for its read and write operations are `DbResultReader` and `DbParamWriter`. Both abstract over the JDBC layer so the `sql` module's `shared` source compiles on Scala.js as well as the JVM, and so custom backends can substitute their own implementations without touching codec logic.
611
+
612
+ ```
613
+ DbCodec[A]
614
+ │ reads via writes via
615
+ ▼ ▼
616
+ DbResultReader DbParamWriter
617
+ │ │
618
+ JdbcResultSetReader JdbcParamWriter
619
+ │ │
620
+ java.sql.ResultSet java.sql.PreparedStatement
621
+ ```
622
+
623
+ ### `DbResultReader` — Result set abstraction
624
+
625
+ `DbResultReader` is the interface through which `DbCodec#readValue` reads column values from a query result. It supports both label-based access (e.g., `DbResultReader#getString("name")`) and 1-based positional access (e.g., `DbResultReader#getInt(1)`), plus `DbResultReader#wasNull` to detect SQL `NULL` after any `get*` call:
626
+
627
+ ```scala
628
+ trait DbResultReader {
629
+ def getInt(index: Int): Int
630
+ def getInt(label: String): Int
631
+ def getString(index: Int): String
632
+ def getString(label: String): String
633
+ def getBoolean(label: String): Boolean
634
+ def getBigDecimal(label: String): java.math.BigDecimal
635
+ def getInstant(label: String): java.time.Instant
636
+ // ... and all other column types
637
+ def columnLabel(index: Int): String
638
+ def hasColumn(label: String): Boolean
639
+ def wasNull: Boolean
640
+ }
641
+ ```
642
+
643
+ `DbResultReader` is rarely used directly in application code. `Frag#query` wraps the JDBC `ResultSet` in a `JdbcResultSetReader` and passes it to the appropriate `DbCodec#readValue` call automatically.
644
+
645
+ ### `DbParamWriter` — Prepared statement abstraction
646
+
647
+ `DbParamWriter` is the interface through which `DbCodec#writeValue` binds column values to a prepared statement. It follows the JDBC convention of 1-based parameter indexes and includes `setNull` for writing SQL `NULL`:
648
+
649
+ ```scala
650
+ trait DbParamWriter {
651
+ def setInt(index: Int, value: Int): Unit
652
+ def setString(index: Int, value: String): Unit
653
+ def setBoolean(index: Int, value: Boolean): Unit
654
+ def setBigDecimal(index: Int, value: java.math.BigDecimal): Unit
655
+ def setInstant(index: Int, value: java.time.Instant): Unit
656
+ // ... and all other parameter types
657
+ def setNull(index: Int, sqlType: Int): Unit
658
+ }
659
+ ```
660
+
661
+ Like `DbResultReader`, `DbParamWriter` is not used directly in application code. `Frag#update` and `Repo` CRUD methods create a `JdbcParamWriter` wrapping a `java.sql.PreparedStatement` and pass it to `DbCodec#writeValue` internally.
662
+
663
+ ## Integration
664
+
665
+ `DbCodec` sits at the center of the `sql` module's layered architecture. The diagram below shows how it connects to its neighbours:
666
+
667
+ ```
668
+ Schema[A]
669
+ │
670
+ ▼ (DbCodecDeriver)
671
+ DbCodec[A] ◄────────────────────────────────┐
672
+ │ │
673
+ ├──► Table[A] │
674
+ │ └──► Repo[E, ID] │
675
+ │ │ │
676
+ │ CRUD methods │
677
+ │ │ │
678
+ └──► Frag ──────────┘ │
679
+ (sql"..." interpolator) │
680
+ │ │
681
+ Transactor#connect/transact │
682
+ │ │
683
+ DbCon / DbTx │
684
+ / \ │
685
+ DbResultReader DbParamWriter ────────────┘
686
+ (readValue) (writeValue)
687
+ ```