@zio.dev/zio-blocks 0.0.21 → 0.0.24

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/index.md CHANGED
@@ -23,6 +23,7 @@ The philosophy is simple: **use what you need, nothing more**. Each block is ind
23
23
  | **Docs** | GitHub Flavored Markdown parsing and rendering | ✅ Available |
24
24
  | **TypeId** | Compile-time type identity with rich metadata | ✅ Available |
25
25
  | **Context** | Type-indexed heterogeneous collections | ✅ Available |
26
+ | **MediaType** | Type-safe IANA media types with 2,600+ predefined types | ✅ Available |
26
27
  | **Streams** | Pull-based streaming primitives | 🚧 In Development |
27
28
 
28
29
  ## Core Principles
@@ -80,14 +81,14 @@ val thriftCodec = Schema[Person].derive(ThriftFormat) // Thrift
80
81
  ### Installation
81
82
 
82
83
  ```scala
83
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.21"
84
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.24"
84
85
 
85
86
  // Optional format modules:
86
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.21"
87
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.21"
88
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.21"
89
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.21"
90
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.21"
87
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.24"
88
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.24"
89
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.24"
90
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.24"
91
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.24"
91
92
  ```
92
93
 
93
94
  ### Example: Optics
@@ -142,7 +143,7 @@ Chunk is designed for:
142
143
  ### Installation
143
144
 
144
145
  ```scala
145
- libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.21"
146
+ libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.24"
146
147
  ```
147
148
 
148
149
  ### Example
@@ -173,7 +174,7 @@ val head: Int = nonEmpty.head // Always safe, no Option needed
173
174
 
174
175
  ## Scope
175
176
 
176
- Compile-time verified resource safety for synchronous Scala code. Scope prevents resource leaks at compile time by tagging values with an unnameable type-level identity—values allocated in a scope can only be used within that scope, and child scope values cannot escape to parent scopes.
177
+ Compile-time verified resource safety for synchronous Scala code. Scope prevents resource leaks at compile time by tagging values with an unnameable type-level identity—values allocated in a scope can only be used within that scope. Child scope values cannot escape to parent scopes, enforced by both the abstract scope-tagged type and the `Unscoped` constraint on `scoped`.
177
178
 
178
179
  ### The Problem
179
180
 
@@ -201,51 +202,56 @@ Using(openDatabase()) { db =>
201
202
  Scope makes resource leaks a **compile error**, not a runtime bug:
202
203
 
203
204
  ```scala
204
- import zio.blocks.scope._
205
+ import zio.blocks.scope.*
205
206
 
206
207
  Scope.global.scoped { scope =>
207
- val db: Database @@ scope.Tag = scope.allocate(Resource(openDatabase()))
208
+ import scope.*
209
+
210
+ val db: $[Database] = allocate(Resource(openDatabase()))
208
211
 
209
212
  // Methods are hidden - can't call db.query() directly
210
- // Must use scope $ to access:
211
- val result = (scope $ db)(_.query("SELECT 1"))
213
+ // Must use $ to access:
214
+ val result: String = $(db)(_.query("SELECT 1"))
212
215
 
213
216
  // Trying to return `db` would be a compile error!
214
- result // Only pure data escapes
217
+ result // Only pure data (String) escapes
215
218
  }
216
219
  // db.close() called automatically
217
220
  ```
218
221
 
219
222
  ### Key Features
220
223
 
221
- - **Compile-Time Leak Prevention**: Values tagged with `A @@ S` can only be used with proof of scope access. Returning a scoped value from its scope is a type error.
222
- - **Zero Runtime Overhead**: On the eager path the `@@` tag is erased—`A @@ S` is represented as just `A` when evaluated—while deferred/composed computations use a small wrapper/thunk.
224
+ - **Compile-Time Leak Prevention**: Values of type `scope.$[A]` are opaque and unique to each scope instance. Returning a scoped value from its scope is a type error.
225
+ - **Zero Runtime Overhead**: `$[A]` erases to `A` at runtime—zero allocation overhead.
223
226
  - **Structured Scopes**: Child scopes nest within parents; resources clean up LIFO when scopes exit.
224
227
  - **Built-in Dependency Injection**: Wire up your application with `Resource.from[T](wires*)` for automatic constructor-based DI.
225
228
  - **AutoCloseable Integration**: Resources implementing `AutoCloseable` have `close()` registered automatically.
229
+ - **Unscoped Constraint**: The `scoped` method requires `Unscoped[A]` evidence on the return type, ensuring only pure data (not resources or closures) can escape.
226
230
 
227
231
  ### Installation
228
232
 
229
233
  ```scala
230
- libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.21"
234
+ libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.24"
231
235
  ```
232
236
 
233
237
  ### Example: Basic Resource Management
234
238
 
235
239
  ```scala
236
- import zio.blocks.scope._
240
+ import zio.blocks.scope.*
237
241
 
238
- final class Database extends AutoCloseable {
242
+ final class Database extends AutoCloseable:
239
243
  def query(sql: String): String = s"Result: $sql"
240
244
  def close(): Unit = println("Database closed")
241
- }
242
245
 
243
246
  Scope.global.scoped { scope =>
244
- // Allocate returns Database @@ scope.Tag (scoped value)
245
- val db = scope.allocate(Resource(new Database))
247
+ import scope.*
248
+
249
+ // Allocate returns $[Database] (scoped value)
250
+ val db: $[Database] = allocate(Resource(new Database))
251
+
252
+ // Access via $ - result (String) escapes, db does not
253
+ val result: String = $(db)(_.query("SELECT * FROM users"))
246
254
 
247
- // Access via scope $ - result (String) escapes, db does not
248
- val result = (scope $ db)(_.query("SELECT * FROM users"))
249
255
  println(result)
250
256
  }
251
257
  // Output: Result: SELECT * FROM users
@@ -255,7 +261,7 @@ Scope.global.scoped { scope =>
255
261
  ### Example: Dependency Injection
256
262
 
257
263
  ```scala
258
- import zio.blocks.scope._
264
+ import zio.blocks.scope.*
259
265
 
260
266
  case class Config(dbUrl: String)
261
267
  class Database(config: Config) extends AutoCloseable { ... }
@@ -269,8 +275,11 @@ val serviceResource: Resource[UserService] = Resource.from[UserService](
269
275
  )
270
276
 
271
277
  Scope.global.scoped { scope =>
272
- val service = scope.allocate(serviceResource)
273
- (scope $ service)(_.createUser("Alice"))
278
+ import scope.*
279
+
280
+ val service = allocate(serviceResource)
281
+
282
+ $(service)(_.createUser("Alice"))
274
283
  }
275
284
  // Cleanup runs LIFO: UserService → Database (UserRepo has no cleanup)
276
285
  ```
@@ -279,13 +288,17 @@ Scope.global.scoped { scope =>
279
288
 
280
289
  ```scala
281
290
  Scope.global.scoped { connScope =>
282
- val conn = connScope.allocate(Resource.fromAutoCloseable(new Connection))
291
+ import connScope.*
292
+
293
+ val conn = allocate(Resource.fromAutoCloseable(new Connection))
283
294
 
284
295
  // Transaction lives in child scope - cleaned up before connection
285
- val result = connScope.scoped { txScope =>
286
- val tx = txScope.allocate(conn.beginTransaction()) // Returns Resource!
287
- (txScope $ tx)(_.execute("INSERT INTO users VALUES (1, 'Alice')"))
288
- (txScope $ tx)(_.commit())
296
+ val result: String = scoped { txScope =>
297
+ import txScope.*
298
+ val c = lower(conn)
299
+ val tx = $(c)(_.beginTransaction()).allocate
300
+ $(tx)(_.execute("INSERT INTO users VALUES (1, 'Alice')"))
301
+ $(tx)(_.commit())
289
302
  "success"
290
303
  }
291
304
  // Transaction closed here, connection still open
@@ -321,7 +334,7 @@ Generating documentation, README files, or any Markdown content programmatically
321
334
  ### Installation
322
335
 
323
336
  ```scala
324
- libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.21"
337
+ libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.24"
325
338
  ```
326
339
 
327
340
  ### Example
@@ -405,7 +418,7 @@ Compile-time type identity with rich metadata. TypeId captures comprehensive inf
405
418
  ### Installation
406
419
 
407
420
  ```scala
408
- libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.21"
421
+ libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.24"
409
422
  ```
410
423
 
411
424
  ### Example
@@ -448,7 +461,7 @@ A type-indexed heterogeneous collection that stores values by their types with c
448
461
  ### Installation
449
462
 
450
463
  ```scala
451
- libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.21"
464
+ libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.24"
452
465
  ```
453
466
 
454
467
  ### Example
@@ -529,11 +542,13 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
529
542
  ### Optics & Navigation
530
543
 
531
544
  - [Optics](./reference/optics.md) - Lenses, prisms, and traversals
545
+ - [SchemaExpr](./reference/schema-expr.md) - Schema-aware expressions for queries and validation
532
546
  - [Path Interpolator](./path-interpolator.md) - Type-safe path construction
533
547
  - [DynamicValue](./reference/dynamic-value.md) - Schema-less dynamic values
534
548
 
535
549
  ### Serialization
536
550
 
551
+ - [Codec & Format](./reference/codec.md) - Codec, Format, BinaryCodec & TextCodec
537
552
  - [JSON](./reference/json.md) - JSON codec and parsing
538
553
  - [JSON Schema](./reference/json-schema.md) - JSON Schema generation and validation
539
554
  - [Formats](./reference/formats.md) - Avro, TOON, MessagePack, BSON, Thrift
@@ -552,3 +567,11 @@ ZIO Blocks supports **Scala 2.13** and **Scala 3.x** with full source compatibil
552
567
  - [TypeId](./reference/typeid.md) - Type identity and metadata
553
568
  - [Context](./reference/context.md) - Type-indexed heterogeneous collections
554
569
  - [Docs (Markdown)](./reference/docs.md) - Markdown parsing and rendering
570
+ - [MediaType](./reference/media-type.md) - Type-safe IANA media types
571
+
572
+ ### Guides
573
+
574
+ - [Query DSL Part 1: Expressions](./guides/query-dsl-reified-optics.md) - Build type-safe, composable query expressions
575
+ - [Query DSL Part 2: SQL Generation](./guides/query-dsl-sql.md) - Translate query expressions into SQL
576
+ - [Query DSL Part 3: Extending the Expression Language](./guides/query-dsl-extending.md) - Add custom operators beyond SchemaExpr
577
+ - [Query DSL Part 4: A Fluent SQL Builder](./guides/query-dsl-fluent-builder.md) - Build type-safe SELECT, UPDATE, INSERT, DELETE statements
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@zio.dev/zio-blocks",
3
3
  "description": "ZIO Blocks Documentation",
4
4
  "license": "Apache-2.0",
5
- "version": "0.0.21",
5
+ "version": "0.0.24",
6
6
  "repository": {
7
7
  "url": "https://github.com/zio/zio-blocks"
8
8
  }
@@ -1,6 +1,7 @@
1
- # Path Interpolator
2
-
3
- ## Overview
1
+ ---
2
+ id: path-interpolator
3
+ title: "Path Interpolator"
4
+ ---
4
5
 
5
6
  The path interpolator `p"..."` is a compile-time string interpolator for constructing `DynamicOptic` instances in ZIO Blocks. It provides a clean, concise syntax for building optic paths that navigate through complex data structures, with all parsing and validation happening at compile time for zero runtime overhead.
6
7
 
@@ -215,14 +216,14 @@ p"<A><B><C>" // Nested variants
215
216
 
216
217
  String and character literals support standard escape sequences:
217
218
 
218
- | Escape | Result | Description |
219
- |--------|--------|-------------|
220
- | `\n` | newline | Line feed |
221
- | `\t` | tab | Horizontal tab |
222
- | `\r` | return | Carriage return |
223
- | `\'` | `'` | Single quote |
224
- | `\"` | `"` | Double quote |
225
- | `\\` | `\` | Backslash |
219
+ | Escape | Result | Description |
220
+ |--------|---------|-----------------|
221
+ | `\n` | newline | Line feed |
222
+ | `\t` | tab | Horizontal tab |
223
+ | `\r` | return | Carriage return |
224
+ | `\'` | `'` | Single quote |
225
+ | `\"` | `"` | Double quote |
226
+ | `\\` | `\` | Backslash |
226
227
 
227
228
  **Examples:**
228
229
 
@@ -618,19 +619,19 @@ val same = p".users[*].email"
618
619
 
619
620
  **Examples:**
620
621
 
621
- | DynamicOptic Construction | toString Output |
622
- |---------------------------|-----------------|
623
- | `DynamicOptic.root.field("name")` | `.name` |
622
+ | DynamicOptic Construction | toString Output |
623
+ |------------------------------------------------------|-------------------|
624
+ | `DynamicOptic.root.field("name")` | `.name` |
624
625
  | `DynamicOptic.root.field("address").field("street")` | `.address.street` |
625
- | `DynamicOptic.root.caseOf("Some")` | `<Some>` |
626
- | `DynamicOptic.root.at(0)` | `[0]` |
627
- | `DynamicOptic.root.atIndices(0, 2, 5)` | `[0,2,5]` |
628
- | `DynamicOptic.elements` | `[*]` |
629
- | `DynamicOptic.root.atKey("host")` | `{"host"}` |
630
- | `DynamicOptic.root.atKey(80)` | `{80}` |
631
- | `DynamicOptic.mapValues` | `{*}` |
632
- | `DynamicOptic.mapKeys` | `{*:}` |
633
- | `DynamicOptic.wrapped` | `.~` |
626
+ | `DynamicOptic.root.caseOf("Some")` | `<Some>` |
627
+ | `DynamicOptic.root.at(0)` | `[0]` |
628
+ | `DynamicOptic.root.atIndices(0, 2, 5)` | `[0,2,5]` |
629
+ | `DynamicOptic.elements` | `[*]` |
630
+ | `DynamicOptic.root.atKey("host")` | `{"host"}` |
631
+ | `DynamicOptic.root.atKey(80)` | `{80}` |
632
+ | `DynamicOptic.mapValues` | `{*}` |
633
+ | `DynamicOptic.mapKeys` | `{*:}` |
634
+ | `DynamicOptic.wrapped` | `.~` |
634
635
 
635
636
  ## Summary
636
637
 
@@ -0,0 +1,386 @@
1
+ ---
2
+ id: codec
3
+ title: "Codec"
4
+ ---
5
+
6
+ `Codec[DecodeInput, EncodeOutput, Value]` is the base abstraction for encoding and decoding values between a specific input representation and a specific output representation. It forms the foundation of ZIO Blocks' multi-format serialization system, enabling a single `Schema[A]` to derive codecs for JSON, Avro, TOON, MessagePack, Thrift, and other formats that are integrated via the `Codec`/`Format` system. BSON support is provided separately via `BsonSchemaCodec`, which is not a subtype of `codec.Codec` and is not derived via `Schema.derive(format)`.
7
+
8
+ ## Overview
9
+
10
+ `Codec` defines two abstract methods that every concrete codec must implement:
11
+
12
+ ```scala
13
+ abstract class Codec[DecodeInput, EncodeOutput, Value] {
14
+ def encode(value: Value, output: EncodeOutput): Unit
15
+ def decode(input: DecodeInput): Either[SchemaError, Value]
16
+ }
17
+ ```
18
+
19
+ - **`encode`** writes the encoded form of `value` into `output`. The output parameter is typically a mutable buffer (`ByteBuffer`, `CharBuffer`) that the caller provides.
20
+ - **`decode`** reads from `input` and returns either a `SchemaError` describing the failure or the decoded value.
21
+
22
+ End users rarely interact with `Codec` directly. Instead, they work with format-specific subclasses like `JsonBinaryCodec[A]` or `ToonBinaryCodec[A]`, which add convenience methods for common input/output types.
23
+
24
+ Given a `Schema[A]`, you can derive a codec for any supported format by calling `Schema[A].derive(format)`, which uses the `Deriver` associated with that format to generate the appropriate codec instance. For example, to derive a JSON codec:
25
+
26
+ ```scala
27
+ import zio.blocks.schema._
28
+ import zio.blocks.schema.json._
29
+
30
+ case class Person(name: String, age: Int)
31
+
32
+ object Person {
33
+ // Derive a schema for Person (required for codec derivation)
34
+ implicit val schema: Schema[Person] = Schema.derived
35
+ // Derive a JSON codec from the schema
36
+ implicit val codec: JsonBinaryCodec[Person] = schema.derive[JsonFormat.type](JsonFormat)
37
+ }
38
+
39
+ // Encode
40
+ val bytes: Array[Byte] = Person.codec.encode(Person("Alice", 30))
41
+
42
+ // Decode
43
+ val result: Either[SchemaError, Person] = Person.codec.decode(bytes)
44
+ ```
45
+
46
+ ## Installation
47
+
48
+ To include the base schema module with JSON support, add the following dependency to your `build.sbt`:
49
+
50
+ ```scala
51
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.24"
52
+ ```
53
+
54
+ Additional format modules are separate artifacts:
55
+
56
+ ```scala
57
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.24"
58
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.24"
59
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.24"
60
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.24"
61
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.24"
62
+ ```
63
+
64
+ For cross-platform projects (Scala.js):
65
+
66
+ ```scala
67
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.24"
68
+ ```
69
+
70
+ Supported Scala versions: 2.13.x and 3.x.
71
+
72
+ ## BinaryCodec and TextCodec
73
+
74
+ The codec system in ZIO Blocks is organized as a layered hierarchy:
75
+
76
+ ```
77
+ Codec[DecodeInput, EncodeOutput, Value]
78
+ ├── BinaryCodec[A] = Codec[ByteBuffer, ByteBuffer, A] (ByteBuffer ↔ A)
79
+ │ ├── JsonBinaryCodec[A]
80
+ │ ├── AvroBinaryCodec[A]
81
+ │ ├── ToonBinaryCodec[A]
82
+ │ ├── ThriftBinaryCodec[A]
83
+ │ └── MessagePackBinaryCodec[A]
84
+ └── TextCodec[A] = Codec[CharBuffer, CharBuffer, A] (CharBuffer ↔ A)
85
+ ```
86
+
87
+ 1. **`BinaryCodec[A]`** fixes both the input and output to `ByteBuffer` and is the base class for all codecs that operate on binary data:
88
+
89
+ ```scala
90
+ abstract class BinaryCodec[A] extends Codec[ByteBuffer, ByteBuffer, A]
91
+ ```
92
+
93
+ 2. **`TextCodec[A]`** fixes both the input and output to `CharBuffer`:
94
+
95
+ ```scala
96
+ abstract class TextCodec[A] extends Codec[CharBuffer, CharBuffer, A]
97
+ ```
98
+
99
+ All built-in serialization formats (JSON, Avro, TOON, MessagePack, Thrift) extend `BinaryCodec`. Despite JSON being a text format, the JSON codec operates on UTF-8 encoded bytes for performance.
100
+
101
+ `TextCodec` exists for formats that operate on character data rather than raw bytes. No built-in formats currently use `TextCodec`, but it is available for custom text-based formats.
102
+
103
+ ## Deriving Codecs
104
+
105
+ ### Using Schema.derive
106
+
107
+ The primary way to obtain a codec is through `Schema[A].derive`:
108
+
109
+ ```scala
110
+ import zio.blocks.schema._
111
+ import zio.blocks.schema.json._
112
+
113
+ case class Person(name: String, age: Int)
114
+ object Person {
115
+ implicit val schema: Schema[Person] = Schema.derived
116
+ }
117
+
118
+ // Pass a Format object to get a codec for that format
119
+ val jsonCodec: JsonBinaryCodec[Person] = Schema[Person].derive[JsonFormat.type](JsonFormat)
120
+ ```
121
+
122
+ This works with any format:
123
+
124
+ ```scala
125
+ import zio.blocks.schema._
126
+ import zio.blocks.schema.json._
127
+ import zio.blocks.schema.toon._
128
+
129
+ case class Person(name: String, age: Int)
130
+ object Person {
131
+ implicit val schema: Schema[Person] = Schema.derived
132
+ }
133
+
134
+ val jsonCodec = Schema[Person].derive(JsonFormat)
135
+ val toonCodec = Schema[Person].derive(ToonFormat)
136
+ ```
137
+
138
+ ### Using Schema.deriving for Customization
139
+
140
+ For more control over the derived codec, use `deriving` to get a `DerivationBuilder`. This lets you override instances for specific substructures or inject modifiers before finalizing:
141
+
142
+ ```scala
143
+ import zio.blocks.schema._
144
+ import zio.blocks.schema.json._
145
+
146
+ case class Person(name: String, age: Int)
147
+ object Person extends CompanionOptics[Person] {
148
+ implicit val schema: Schema[Person] = Schema.derived
149
+ val name = $(_.name)
150
+ val age = $(_.age)
151
+ }
152
+
153
+ // Override the codec for the "name" field
154
+ val customNameCodec = new JsonBinaryCodec[String] {
155
+ def decodeValue(in: JsonReader, default: String): String = in.readString(default)
156
+ def encodeValue(x: String, out: JsonWriter): Unit = out.writeVal(x.toUpperCase)
157
+ }
158
+
159
+ val codec: JsonBinaryCodec[Person] = Schema[Person]
160
+ .deriving(JsonFormat.deriver)
161
+ .instance(Person.name, customNameCodec)
162
+ .derive
163
+ ```
164
+
165
+ ### Using Schema#decode and Schema#encode
166
+
167
+ `Schema` also provides `decode` and `encode` methods that internally call `derive` (with caching) and then delegate to the codec:
168
+
169
+ ```scala
170
+ import zio.blocks.schema._
171
+ import zio.blocks.schema.json._
172
+ import java.nio.ByteBuffer
173
+
174
+ case class Person(name: String, age: Int)
175
+ object Person {
176
+ implicit val schema: Schema[Person] = Schema.derived
177
+ }
178
+
179
+ // Encode directly from Schema
180
+ val buffer = ByteBuffer.allocate(1024)
181
+ Schema[Person].encode(JsonFormat)(buffer)(Person("Alice", 30))
182
+
183
+ // Decode directly from Schema
184
+ buffer.flip()
185
+ val result: Either[SchemaError, Person] = Schema[Person].decode(JsonFormat)(buffer)
186
+ ```
187
+
188
+ ### Using a Deriver Directly
189
+
190
+ Each `Format` object contains a `Deriver[TC]` that can also be passed to `derive`:
191
+
192
+ ```scala
193
+ import zio.blocks.schema._
194
+ import zio.blocks.schema.json._
195
+
196
+ case class Person(name: String, age: Int)
197
+ object Person {
198
+ implicit val schema: Schema[Person] = Schema.derived
199
+ }
200
+
201
+ // These are equivalent:
202
+ val codec1 = Schema[Person].derive(JsonFormat)
203
+ val codec2 = Schema[Person].derive(JsonFormat.deriver)
204
+ ```
205
+
206
+ Passing a `Deriver` directly is useful when working with custom or configured derivers (see [Configuring Codecs](#configuring-codecs)).
207
+
208
+ ## Convenience Methods on Format-Specific Codecs
209
+
210
+ While the base `Codec` class defines only `encode(value, output)` and `decode(input)`, format-specific subclasses like `JsonBinaryCodec` and `ToonBinaryCodec` add convenience overloads for common I/O types.
211
+
212
+ ### JsonBinaryCodec Convenience Methods
213
+
214
+ `JsonBinaryCodec[A]` provides the following overloads beyond the base `ByteBuffer` API:
215
+
216
+ ```scala
217
+ import zio.blocks.schema._
218
+ import zio.blocks.schema.json._
219
+
220
+ case class Person(name: String, age: Int)
221
+ object Person {
222
+ implicit val schema: Schema[Person] = Schema.derived
223
+ }
224
+
225
+ val codec = Schema[Person].derive(JsonFormat)
226
+ val person = Person("Alice", 30)
227
+
228
+ // Array[Byte]
229
+ val bytes: Array[Byte] = codec.encode(person)
230
+ val fromBytes1: Either[SchemaError, Person] = codec.decode(bytes)
231
+ val fromBytes2: Either[SchemaError, Person] = codec.decode(bytes, 0, bytes.length)
232
+
233
+ // String
234
+ val jsonStr: String = codec.encodeToString(person)
235
+ val fromStr: Either[SchemaError, Person] = codec.decode("""{"name":"Alice","age":30}""")
236
+
237
+ // InputStream / OutputStream
238
+ import java.io.{ByteArrayInputStream, ByteArrayOutputStream}
239
+
240
+ val os = new ByteArrayOutputStream()
241
+ codec.encode(person, os)
242
+
243
+ val is = new ByteArrayInputStream(os.toByteArray)
244
+ val fromStream: Either[SchemaError, Person] = codec.decode(is)
245
+ ```
246
+
247
+ ### ToonBinaryCodec Convenience Methods
248
+
249
+ `ToonBinaryCodec[A]` provides the same set of overloads:
250
+
251
+ ```scala
252
+ import zio.blocks.schema._
253
+ import zio.blocks.schema.toon._
254
+
255
+ case class Person(name: String, age: Int)
256
+ object Person {
257
+ implicit val schema: Schema[Person] = Schema.derived
258
+ }
259
+
260
+ val codec = Schema[Person].derive(ToonFormat)
261
+ val person = Person("Alice", 30)
262
+
263
+ // Array[Byte]
264
+ val bytes: Array[Byte] = codec.encode(person)
265
+ val fromBytes: Either[SchemaError, Person] = codec.decode(bytes)
266
+
267
+ // String
268
+ val toonStr: String = codec.encodeToString(person)
269
+ val fromStr: Either[SchemaError, Person] = codec.decode("name: Alice\nage: 30")
270
+ ```
271
+
272
+ ### Summary of Convenience Methods
273
+
274
+ `BinaryCodec` subclasses (JSON, TOON, MessagePack, Avro, Thrift) expose the following convenience overloads (availability may vary by format):
275
+
276
+ | Method | Description |
277
+ |--------------------------------------------------------------------------|--------------------------------------------------|
278
+ | `encode(value): Array[Byte]` | Encode to a byte array |
279
+ | `decode(input: Array[Byte]): Either[SchemaError, A]` | Decode from a byte array |
280
+ | `decode(input: Array[Byte], from: Int, to: Int): Either[SchemaError, A]` | Decode from a byte array slice |
281
+ | `encode(value, output: ByteBuffer): Unit` | Encode into a `ByteBuffer` |
282
+ | `decode(input: ByteBuffer): Either[SchemaError, A]` | Decode from a `ByteBuffer` |
283
+ | `encode(value, output: OutputStream): Unit` | Encode into an `OutputStream` (JSON, TOON, Avro) |
284
+ | `decode(input: InputStream): Either[SchemaError, A]` | Decode from an `InputStream` (JSON, TOON, Avro) |
285
+ | `encodeToString(value): String` | Encode to a `String` (JSON, TOON) |
286
+ | `decode(input: String): Either[SchemaError, A]` | Decode from a `String` (JSON, TOON) |
287
+
288
+ The `String`-based methods are available on text-oriented binary codecs (JSON, TOON) but not on purely binary formats like Avro or Thrift.
289
+
290
+ ## Configuring Codecs
291
+
292
+ Format-specific derivers support configuration options that control encoding behavior. Instead of passing a `Format` object to `derive`, you pass a configured `Deriver`:
293
+
294
+ ### JSON Configuration
295
+
296
+ ```scala
297
+ import zio.blocks.schema._
298
+ import zio.blocks.schema.json._
299
+
300
+ case class Person(
301
+ firstName: String,
302
+ lastName: String,
303
+ middleName: Option[String] = None
304
+ )
305
+
306
+ object Person {
307
+ implicit val schema: Schema[Person] = Schema.derived
308
+ }
309
+
310
+ val customDeriver = JsonBinaryCodecDeriver
311
+ .withFieldNameMapper(NameMapper.SnakeCase)
312
+ .withTransientNone(true)
313
+ .withRejectExtraFields(true)
314
+
315
+ val codec = Schema[Person].derive(customDeriver)
316
+
317
+ // Encodes as: {"first_name":"Alice","last_name":"Smith"}
318
+ // (middleName omitted because it is None and transientNone is true)
319
+ val json = codec.encodeToString(Person("Alice", "Smith"))
320
+ ```
321
+
322
+ | Option | Description | Default |
323
+ |---------------------------------|--------------------------------------------------------|------------|
324
+ | `withFieldNameMapper` | Transform field names (Identity, SnakeCase, KebabCase) | `Identity` |
325
+ | `withCaseNameMapper` | Transform variant/case names | `Identity` |
326
+ | `withDiscriminatorKind` | ADT discriminator style (Key, Field, None) | `Key` |
327
+ | `withRejectExtraFields` | Error on unknown fields during decoding | `false` |
328
+ | `withEnumValuesAsStrings` | Encode enum values as strings | `true` |
329
+ | `withTransientNone` | Omit `None` values from output | `true` |
330
+ | `withTransientEmptyCollection` | Omit empty collections from output | `true` |
331
+ | `withTransientDefaultValue` | Omit fields with default values | `true` |
332
+ | `withRequireOptionFields` | Require optional fields in input | `false` |
333
+ | `withRequireCollectionFields` | Require collection fields in input | `false` |
334
+ | `withRequireDefaultValueFields` | Require fields with defaults in input | `false` |
335
+
336
+ ### TOON Configuration
337
+
338
+ ```scala
339
+ import zio.blocks.schema._
340
+ import zio.blocks.schema.toon._
341
+
342
+ case class Person(
343
+ firstName: String,
344
+ lastName: String
345
+ )
346
+
347
+ object Person {
348
+ implicit val schema: Schema[Person] = Schema.derived
349
+ }
350
+
351
+ val customDeriver = ToonBinaryCodecDeriver
352
+ .withFieldNameMapper(NameMapper.SnakeCase)
353
+ .withArrayFormat(ArrayFormat.Tabular)
354
+ .withDiscriminatorKind(DiscriminatorKind.Field("type"))
355
+
356
+ val codec = Schema[Person].derive(customDeriver)
357
+ ```
358
+
359
+ ## Error Handling
360
+
361
+ All `decode` operations return `Either[SchemaError, A]`. `SchemaError` includes path information that pinpoints where in the data structure decoding failed:
362
+
363
+ ```scala
364
+ import zio.blocks.schema._
365
+ import zio.blocks.schema.json._
366
+
367
+ case class Address(street: String, city: String)
368
+ case class Person(name: String, address: Address)
369
+
370
+ object Address {
371
+ implicit val schema: Schema[Address] = Schema.derived
372
+ }
373
+ object Person {
374
+ implicit val schema: Schema[Person] = Schema.derived
375
+ }
376
+
377
+ val codec = Schema[Person].derive(JsonFormat)
378
+
379
+ // Missing required field
380
+ val result = codec.decode("""{"name":"Alice","address":{}}""")
381
+
382
+ result match {
383
+ case Right(person) => println(person)
384
+ case Left(error) => error.errors.foreach(e => println(s"Error: ${e.message}"))
385
+ }
386
+ ```