@zio.dev/zio-blocks 0.0.51 → 0.0.55

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 (164) hide show
  1. package/adr/2026-07-18-data-migration.md +123 -0
  2. package/guides/async-getting-started.md +687 -0
  3. package/guides/compile-time-resource-safety-with-scope.md +6 -0
  4. package/guides/getting-started-with-mux.md +0 -112
  5. package/guides/query-dsl-extending.md +1 -1
  6. package/guides/query-dsl-fluent-builder.md +1 -1
  7. package/guides/query-dsl-reified-optics.md +1 -1
  8. package/guides/query-dsl-sql.md +395 -1
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +131 -70
  12. package/guides/zio-schema-migration.md +6 -6
  13. package/index.md +200 -583
  14. package/package.json +1 -1
  15. package/reference/async.md +1379 -531
  16. package/reference/chunk.md +3 -3
  17. package/reference/codegen/index.md +1 -1
  18. package/reference/combinators.md +4 -4
  19. package/reference/config/config-decoder.md +460 -0
  20. package/reference/config/config-source.md +489 -0
  21. package/reference/config/errors.md +278 -0
  22. package/reference/config/flags.md +369 -0
  23. package/reference/config/formats.md +314 -0
  24. package/reference/config/index.md +304 -0
  25. package/reference/config/rollout.md +336 -0
  26. package/reference/context.md +6 -49
  27. package/reference/data-migration.md +269 -0
  28. package/reference/datastar/attributes.md +302 -0
  29. package/reference/datastar/events.md +234 -0
  30. package/reference/datastar/index.md +256 -0
  31. package/reference/datastar/signals.md +230 -0
  32. package/reference/datastar/sse.md +295 -0
  33. package/reference/datastar.md +2 -2
  34. package/reference/docs.md +2 -2
  35. package/reference/endpoint/bulk-creation.md +96 -0
  36. package/reference/endpoint/index.md +9 -89
  37. package/reference/endpoint/path-codec.md +12 -24
  38. package/reference/endpoint/route-pattern.md +4 -6
  39. package/reference/endpoint/segment-codec.md +19 -32
  40. package/reference/html.md +313 -9
  41. package/reference/htmx/index.md +4 -52
  42. package/reference/htmx/response-headers.md +240 -0
  43. package/reference/http-model/headers.md +735 -0
  44. package/reference/http-model/index.md +3 -1
  45. package/reference/http-model/model.md +107 -71
  46. package/reference/http-model/schema-codecs.md +522 -0
  47. package/reference/http-model/schema.md +6 -3
  48. package/reference/http-model/server-sent-event.md +341 -0
  49. package/reference/jwt.md +195 -0
  50. package/reference/maybe.md +128 -11
  51. package/reference/media-type.md +2 -2
  52. package/reference/mux.md +254 -0
  53. package/reference/mux.mdx +7 -2
  54. package/reference/openapi.md +3 -3
  55. package/reference/projection.md +654 -0
  56. package/reference/resource-management/resource.md +2 -98
  57. package/reference/resource-management/scope.md +1 -209
  58. package/reference/resource-management/wire.md +4 -50
  59. package/reference/ringbuffer/advanced.mdx +1 -1
  60. package/reference/ringbuffer/index.mdx +3 -3
  61. package/reference/ringbuffer/mpmc.mdx +38 -4
  62. package/reference/ringbuffer/mpsc.mdx +36 -4
  63. package/reference/ringbuffer/spmc.mdx +1 -1
  64. package/reference/ringbuffer/spsc.mdx +87 -15
  65. package/reference/schema/allows.md +0 -96
  66. package/reference/schema/binding.md +2 -2
  67. package/reference/schema/built-in-codecs/avro.md +2 -2
  68. package/reference/schema/built-in-codecs/bson.md +50 -20
  69. package/reference/schema/built-in-codecs/csv.md +2 -2
  70. package/reference/schema/built-in-codecs/index.md +3 -3
  71. package/reference/schema/built-in-codecs/json/index.md +2 -2
  72. package/reference/schema/built-in-codecs/messagepack.md +3 -3
  73. package/reference/schema/built-in-codecs/thrift.md +2 -2
  74. package/reference/schema/built-in-codecs/toon.md +3 -3
  75. package/reference/schema/built-in-codecs/yaml.md +2 -2
  76. package/reference/schema/codec.md +11 -11
  77. package/reference/schema/dynamic-optic.md +48 -3
  78. package/reference/schema/dynamic-schema.md +3 -3
  79. package/reference/schema/index.md +2 -0
  80. package/reference/schema/path-interpolator.md +2 -0
  81. package/reference/schema/reflect-transformer.md +140 -0
  82. package/reference/schema/schema-evolution/as.md +4 -4
  83. package/reference/schema/schema-evolution/into.md +2 -2
  84. package/reference/schema/schema-expr.md +2 -2
  85. package/reference/schema/schema-search.md +263 -0
  86. package/reference/schema/schema.md +10 -2
  87. package/reference/schema/type-class-derivation.md +1 -1
  88. package/reference/smithy.md +502 -3
  89. package/reference/sql/db-codec-deriver.md +3 -3
  90. package/reference/sql/db-codec.md +22 -22
  91. package/reference/sql/db-con.md +4 -4
  92. package/reference/sql/db-connection.md +1 -1
  93. package/reference/sql/db-param.md +1 -1
  94. package/reference/sql/db-result-reader.md +4 -2
  95. package/reference/sql/db-tx.md +46 -14
  96. package/reference/sql/ddl.md +1 -1
  97. package/reference/sql/frag.md +44 -10
  98. package/reference/sql/index.md +7 -7
  99. package/reference/sql/repo.md +15 -15
  100. package/reference/sql/sql-dialect.md +1 -1
  101. package/reference/sql/sql-logger.md +1 -1
  102. package/reference/sql/sql-name-mapper.md +3 -3
  103. package/reference/sql/table-metadata.md +3 -3
  104. package/reference/sql/table.md +10 -10
  105. package/reference/sql/transactor-zio.md +1 -1
  106. package/reference/sql/transactor.md +21 -11
  107. package/reference/sql-zio.md +1 -1
  108. package/reference/streams/core/index.md +32 -0
  109. package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
  110. package/reference/streams/{sink.md → core/sink.md} +331 -353
  111. package/reference/streams/{stream.md → core/stream.md} +919 -209
  112. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  113. package/reference/streams/execution-and-compatibility/index.md +35 -0
  114. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  115. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  116. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  117. package/reference/streams/index.md +140 -67
  118. package/reference/streams/primitives/index.md +30 -0
  119. package/reference/streams/primitives/reader.md +1992 -0
  120. package/reference/streams/{writer.md → primitives/writer.md} +254 -98
  121. package/reference/telemetry/common/any-value.md +90 -0
  122. package/reference/telemetry/common/attribute-key.md +87 -0
  123. package/reference/telemetry/common/attributes.md +118 -0
  124. package/reference/telemetry/common/index.md +39 -0
  125. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  126. package/reference/telemetry/common/resource.md +34 -0
  127. package/reference/telemetry/index.md +311 -0
  128. package/reference/telemetry/logging/index.md +197 -0
  129. package/reference/telemetry/logging/log-enrichment.md +72 -0
  130. package/reference/telemetry/logging/log-formatter.md +100 -0
  131. package/reference/telemetry/logging/log-record-processor.md +56 -0
  132. package/reference/telemetry/logging/log-record.md +44 -0
  133. package/reference/telemetry/logging/log-writer.md +64 -0
  134. package/reference/telemetry/logging/logger-provider.md +142 -0
  135. package/reference/telemetry/logging/logger.md +83 -0
  136. package/reference/telemetry/logging/severity.md +62 -0
  137. package/reference/telemetry/metrics/index.md +150 -0
  138. package/reference/telemetry/metrics/instruments.md +183 -0
  139. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  140. package/reference/telemetry/metrics/meter-provider.md +76 -0
  141. package/reference/telemetry/metrics/meter.md +98 -0
  142. package/reference/telemetry/metrics/metric-data.md +57 -0
  143. package/reference/telemetry/otel/custom-exporter.md +216 -0
  144. package/reference/telemetry/otel/index.md +212 -0
  145. package/reference/telemetry/tracing/index.md +155 -0
  146. package/reference/telemetry/tracing/sampler.md +89 -0
  147. package/reference/telemetry/tracing/span-builder.md +57 -0
  148. package/reference/telemetry/tracing/span-context.md +39 -0
  149. package/reference/telemetry/tracing/span-data.md +32 -0
  150. package/reference/telemetry/tracing/span-kind.md +55 -0
  151. package/reference/telemetry/tracing/span-processor.md +53 -0
  152. package/reference/telemetry/tracing/span-status.md +47 -0
  153. package/reference/telemetry/tracing/span.md +117 -0
  154. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  155. package/reference/telemetry/tracing/tracer.md +52 -0
  156. package/reference/typeid.md +0 -64
  157. package/sidebars.js +150 -12
  158. package/undocumented-report.md +528 -270
  159. package/reference/config.md +0 -158
  160. package/reference/streams/concurrent-operators.md +0 -106
  161. package/reference/streams/reader.md +0 -1284
  162. package/reference/streams/scala-2-compatibility.md +0 -55
  163. package/reference/streams/zero-boxing.md +0 -275
  164. package/reference/telemetry.md +0 -693
@@ -28,13 +28,13 @@ Rather than hand-writing TOON parsing logic or relying on external libraries wit
28
28
  Add the module to your `build.sbt`:
29
29
 
30
30
  ```sbt
31
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.51"
31
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.55"
32
32
  ```
33
33
 
34
34
  For Scala.js, use `%%%` instead of `%%`:
35
35
 
36
36
  ```sbt
37
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema-toon" % "0.0.51"
37
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema-toon" % "0.0.55"
38
38
  ```
39
39
 
40
40
  Supported Scala versions: 2.13.x and 3.x
@@ -325,7 +325,7 @@ object Person {
325
325
  }
326
326
 
327
327
  val codec = Person.schema.derive(ToonFormat)
328
- // codec: ToonCodec[Person] = zio.blocks.schema.toon.ToonCodecDeriver$$anon$5@3163694a
328
+ // codec: ToonCodec[Person] = zio.blocks.schema.toon.ToonCodecDeriver$$anon$5@7c8849a3
329
329
  val person = Person("Alice", "alice@example.com")
330
330
  // person: Person = Person(name = "Alice", email = "alice@example.com")
331
331
  val bytes = codec.encode(person)
@@ -27,13 +27,13 @@ Rather than hand-writing YAML parsing logic or relying on external libraries wit
27
27
  Add the module to your `build.sbt`:
28
28
 
29
29
  ```sbt
30
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-yaml" % "0.0.51"
30
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-yaml" % "0.0.55"
31
31
  ```
32
32
 
33
33
  For Scala.js, use `%%%` instead of `%%`:
34
34
 
35
35
  ```sbt
36
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema-yaml" % "0.0.51"
36
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema-yaml" % "0.0.55"
37
37
  ```
38
38
 
39
39
  Supported Scala versions: 2.13.x and 3.x
@@ -3,7 +3,7 @@ id: codec
3
3
  title: "Codec"
4
4
  ---
5
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)`.
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 integrated through `Codec`/`Format`. BSON's `BsonCodec` is not a subtype of `codec.Codec` because it operates on native `BsonReader`, `BsonWriter`, and `BsonValue` APIs. It nevertheless participates in shared type-class derivation through `Schema[A].derive(BsonCodecDeriver)`; `BsonSchemaCodec` remains a compatibility facade.
7
7
 
8
8
  ## Overview
9
9
 
@@ -48,26 +48,26 @@ val result: Either[SchemaError, Person] = Person.codec.decode(bytes)
48
48
  To include the base schema module with JSON support, add the following dependency to your `build.sbt`:
49
49
 
50
50
  ```scala
51
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
51
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.55"
52
52
  ```
53
53
 
54
54
  Additional format modules are separate artifacts:
55
55
 
56
56
  ```scala
57
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.51"
58
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.51"
59
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-csv" % "0.0.51"
60
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.51"
61
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.51"
62
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.51"
63
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-xml" % "0.0.51"
64
- libraryDependencies += "dev.zio" %% "zio-blocks-schema-yaml" % "0.0.51"
57
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.55"
58
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.55"
59
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-csv" % "0.0.55"
60
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.55"
61
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.55"
62
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.55"
63
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-xml" % "0.0.55"
64
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema-yaml" % "0.0.55"
65
65
  ```
66
66
 
67
67
  For cross-platform projects (Scala.js):
68
68
 
69
69
  ```scala
70
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
70
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.55"
71
71
  ```
72
72
 
73
73
  Supported Scala versions: 2.13.x and 3.x.
@@ -398,7 +398,7 @@ val serialized: DynamicValue = opticSchema.toDynamicValue(path)
398
398
 
399
399
  ## Search Optics
400
400
 
401
- A **Search optic** recursively traverses a data structure to find **all occurrences** matching a type or schema. It produces a `Traversal[S, A]` that collects matches in depth-first, left-to-right order.
401
+ A **Search optic** recursively traverses a data structure to find **all occurrences** matching a type or schema. It produces a `Traversal[S, A]` that collects matches in depth-first, left-to-right order. This section covers the two DSLs for building a search path; for the `SearchTraversal` engine that executes one — `fold`/`modify`/`check`, composing a search with other optics, and the `SchemaMatch` predicate behind `#Pattern` matching — see [Schema Search and Update](./schema-search.md).
402
402
 
403
403
  ### Motivation
404
404
 
@@ -483,6 +483,51 @@ val modified = data.modify(p"#string")(dv =>
483
483
  )
484
484
  ```
485
485
 
486
+ ### The `SchemaRepr` Pattern Type
487
+
488
+ Every `#Pattern` string above parses into a `SchemaRepr` — a plain, immutable ADT you can also build by hand, independent of the interpolator. `DynamicOptic#searchSchema(schemaRepr)` takes one directly, and `Node.SchemaSearch(schemaRepr)` is the node it produces, exactly the node `#Pattern` syntax builds under the hood:
489
+
490
+ ```scala
491
+ import zio.blocks.schema._
492
+
493
+ val pattern: SchemaRepr = SchemaRepr.Record(
494
+ IndexedSeq("name" -> SchemaRepr.Primitive("string"), "age" -> SchemaRepr.Primitive("int"))
495
+ )
496
+ ```
497
+
498
+ `SchemaRepr#toString` renders the same syntax `#Pattern` strings use, so a pattern built by hand and one parsed from a literal print identically:
499
+
500
+ ```scala
501
+ pattern.toString
502
+ // res13: String = "record { name: string, age: int }"
503
+
504
+ val path: DynamicOptic = DynamicOptic.root.searchSchema(pattern)
505
+ // path: DynamicOptic = DynamicOptic(
506
+ // IndexedSeq(
507
+ // SchemaSearch(
508
+ // Record(Vector(("name", Primitive("string")), ("age", Primitive("int"))))
509
+ // )
510
+ // )
511
+ // )
512
+ path == p"#record { name: string, age: int }"
513
+ // res14: Boolean = true
514
+ ```
515
+
516
+ The eight cases cover every shape the grammar recognizes:
517
+
518
+ | Case | Renders as | Matches |
519
+ |------|------------|---------|
520
+ | `Nominal(name)` | the bare name, e.g. `Person` | a value of that Scala type — only against a `Reflect` tree, see below |
521
+ | `Primitive(name)` | the bare name, e.g. `string` | a primitive of that kind (case-insensitive) |
522
+ | `Record(fields)` | `record { name: string, ... }` | a record with at least those fields, by subset match |
523
+ | `Variant(cases)` | `variant { Case: type, ... }` | a variant whose active case matches one of those entries |
524
+ | `Sequence(element)` | `list(...)` | a sequence whose elements all match — parses from `list(...)`, `set(...)`, or `vector(...)`, all three producing the same case |
525
+ | `Map(key, value)` | `map(key, value)` | a map whose entries all match both patterns |
526
+ | `Optional(inner)` | `option(...)` | `Null`, or a value matching the inner pattern |
527
+ | `Wildcard` | `_` | anything |
528
+
529
+ Parsing a `#Pattern` string is itself layered: the path interpolator recognizes the `#` prefix and hands the substring after it to a small recursive-descent parser, which reports a specific error (unexpected character, an empty `record {}`/`variant {}`, an unterminated expression) at the exact offset where parsing failed — that's what turns a typo like `#record { name: strnig }` into a compile-time interpolator error rather than a pattern that silently matches nothing.
530
+
486
531
  ### Supported Patterns
487
532
 
488
533
  Search optics support both **type-based** and **schema-based** patterns:
@@ -493,7 +538,7 @@ Search optics support both **type-based** and **schema-based** patterns:
493
538
  | `#int`, `#string`, `#boolean` | Specific primitive types (case-insensitive) | `#int`, `#string`, `#boolean` |
494
539
  | `#record { ... }` | Records with matching fields | `#record { name: string }` |
495
540
  | `#variant { ... }` | Variant cases with matching content | `#variant { Error: string }` |
496
- | `#list(...)` | Lists/sequences with element type | `#list(string)` |
541
+ | `#list(...)`, `#set(...)`, `#vector(...)` | Sequences with a matching element type, regardless of which of the three keywords appears | `#list(string)` |
497
542
  | `#map(...)` | Maps with key and value types | `#map(string, int)` |
498
543
  | `#option(...)` | Optional values with inner type | `#option(int)` |
499
544
 
@@ -520,7 +565,7 @@ val allInts = nested.get(p"#int").toChunk
520
565
 
521
566
  ### Known Limitation: Nominal Matching in Untyped Contexts
522
567
 
523
- `Nominal` pattern matching (e.g., `#Person`) returns `false` when applied to `DynamicValue` or `Json` because these untyped structures carry no type identity. To match nominally-typed data, use one of these approaches:
568
+ `Nominal` pattern matching (e.g., `#Person`) returns `false` against a `DynamicValue` or `Json` value because these untyped structures carry no type identity. The one exception is searching a `Schema`/`Reflect` tree itself (as `DynamicSchema#get` does) rather than a value — there, `Nominal` matches by comparing against the tree's own `TypeId`, since a `Reflect` node still carries the type identity a plain `DynamicValue`/`Json` value never does. For searching values, use one of these approaches instead:
524
569
 
525
570
  - **Use the typed API**: `optic(_.searchFor[Person])`
526
571
  - **Or use structural patterns**: `p"#record { name: string, age: int }"`
@@ -83,7 +83,7 @@ dynamic
83
83
  // value = Primitive(
84
84
  // primitiveType = String(None),
85
85
  // typeId = String,
86
- // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@480c1083,
86
+ // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5fb4c2ef,
87
87
  // doc = Doc(blocks = IndexedSeq(), metadata = Map()),
88
88
  // modifiers = List(),
89
89
  // storedDefaultValue = None,
@@ -97,7 +97,7 @@ dynamic
97
97
  // value = Primitive(
98
98
  // primitiveType = Int(None),
99
99
  // typeId = Int,
100
- // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@480c1083,
100
+ // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5fb4c2ef,
101
101
  // doc = Doc(blocks = IndexedSeq(), metadata = Map()),
102
102
  // modifiers = List(),
103
103
  // storedDefaultValue = None,
@@ -115,7 +115,7 @@ dynamic
115
115
  // value = Primitive(
116
116
  // primitiveType = String(None),
117
117
  // typeId = String,
118
- // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@480c1083,
118
+ // primitiveBinding = zio.blocks.schema.binding.NoBinding$$anon$1@5fb4c2ef,
119
119
  // doc = Doc(blocks = IndexedSeq(), metadata = Map()),
120
120
  // modifiers = List(),
121
121
  // storedDefaultValue = None,
@@ -12,6 +12,7 @@ ZIO Blocks Schema is the core type system and serialization framework that provi
12
12
  - [`Schema`](./schema.md) — Primary data type containing reified structure of a Scala data type
13
13
  - [`Reflect`](./reflect.md) — Foundational data structure containing reified structural information
14
14
  - [`Binding`](./binding.md) — Operational machinery for constructing and deconstructing values
15
+ - [`ReflectTransformer`](./reflect-transformer.md) — Generic mechanism for rewriting a Reflect tree's binding type, powering schema (de)serialization and derivation
15
16
  - [`Registers`](./registers.md) — Register-based design for zero-allocation construction and deconstruction
16
17
  - [`Structural Types`](./structural-types.md) — Duck typing with ZIO Blocks schemas
17
18
 
@@ -24,6 +25,7 @@ ZIO Blocks Schema is the core type system and serialization framework that provi
24
25
  - [`Optics`](./optics.md) — Reflective optics for type-safe, composable access to nested data structures
25
26
  - [`DynamicOptic`](./dynamic-optic.md) — Runtime path through nested data structures
26
27
  - [`Path Interpolator`](./path-interpolator.md) — Compile-time string interpolator `p"..."` for constructing `DynamicOptic` instances
28
+ - [`Schema Search and Update`](./schema-search.md) — Executing type/schema-pattern searches with `SearchTraversal`, and rewriting schema metadata with `Reflect.Updater`/`Term.Updater`
27
29
  - [`SchemaExpr`](./schema-expr.md) — Schema-aware expressions for evaluation and query language translation
28
30
  - [`Patch`](./patch.md) — Type-safe, serializable transformations of data structures
29
31
  - [`Modifier`](./modifier.md) — Mechanism to attach metadata and configuration to schema elements
@@ -267,6 +267,8 @@ p"#map(string, int)" // Find maps from string to int
267
267
  p"#option(Person)" // Find optional Person values
268
268
  ```
269
269
 
270
+ `set(...)` and `vector(...)` work as synonyms for `list(...)` — all three parse to the same `SchemaRepr.Sequence` pattern, since the pattern only cares about the element type, not which collection it comes from.
271
+
270
272
  To match any value regardless of type:
271
273
 
272
274
  ```scala
@@ -0,0 +1,140 @@
1
+ ---
2
+ id: reflect-transformer
3
+ title: "ReflectTransformer"
4
+ ---
5
+
6
+ `ReflectTransformer[F, G]` rewrites a `Reflect[F, A]` tree into a `Reflect[G, A]` tree, changing the binding-metadata type constructor from `F` to `G` at every node while leaving the shape — field names, type IDs, docs, modifiers, defaults, examples — untouched. `Reflect#transform` walks the tree and recurses into fields, cases, elements, and keys/values for you; a `ReflectTransformer` only has to say how to rebuild the metadata-bearing leaf of each node kind once its children are already transformed.
7
+
8
+ Three things in the codebase are built on it: stripping bindings to serialize a [`Schema`](./schema.md) as a [`DynamicSchema`](./dynamic-schema.md), attaching bindings back with `RebindTransformer`, and [type class derivation](./type-class-derivation.md), which pairs each node's binding with a derived instance. This page covers all three, plus `OnlyMetadata`, the base class most transformers use instead of implementing the full interface.
9
+
10
+ ## Design & Structure
11
+
12
+ `ReflectTransformer` declares one method per [`Reflect`](./reflect.md) node kind — seven of the eight; `Deferred` is handled separately (see [Cycle Safety](#cycle-safety-and-the-transform-cache)) because it has no metadata of its own to transform. `transformRecord` is representative of all seven — each takes the node's already-transformed children plus its untouched shape fields, and returns a `Lazy` of the rebuilt node:
13
+
14
+ ```scala
15
+ trait ReflectTransformer[-F[_, _], G[_, _]] {
16
+ def transformRecord[A](
17
+ path: DynamicOptic,
18
+ fields: IndexedSeq[Term[G, A, ?]],
19
+ typeId: TypeId[A],
20
+ metadata: F[BindingType.Record, A],
21
+ doc: Doc,
22
+ modifiers: Seq[Modifier.Reflect],
23
+ storedDefaultValue: Option[DynamicValue],
24
+ storedExamples: collection.immutable.Seq[DynamicValue]
25
+ ): Lazy[Reflect.Record[G, A]]
26
+
27
+ // ...one more method per remaining node kind, same shape
28
+ }
29
+ ```
30
+
31
+ The other six follow the identical pattern — a `path`, the already-transformed children, the shape fields, and the still-untransformed `metadata: F[...]` — differing only in which children they carry and which `Reflect` node they return:
32
+
33
+ | Node kind | Method | Transformed children it receives |
34
+ | ---------- | -------- | ----------------------------------- |
35
+ | `Record` | `transformRecord` | `fields: IndexedSeq[Term[G, A, ?]]` |
36
+ | `Variant` | `transformVariant` | `cases: IndexedSeq[Term[G, A, ? <: A]]` |
37
+ | `Sequence` | `transformSequence` | `element: Reflect[G, A]` |
38
+ | `Map` | `transformMap` | `key: Reflect[G, Key]`, `value: Reflect[G, Value]` |
39
+ | `Dynamic` | `transformDynamic` | none — `DynamicValue` has no children |
40
+ | `Primitive` | `transformPrimitive` | none |
41
+ | `Wrapper` | `transformWrapper` | `wrapped: Reflect[G, B]` |
42
+
43
+ Every result is wrapped in [`Lazy`](./lazy.md) because a transformer built for a recursive schema must be able to build a node before its own children are fully forced.
44
+
45
+ ## `OnlyMetadata` — The Common Case
46
+
47
+ Most transformers only need to change the metadata and leave every field, case, and element exactly as it already was. `ReflectTransformer.OnlyMetadata[F, G]` implements all seven `transformX` methods for you in terms of one method:
48
+
49
+ ```scala
50
+ abstract class OnlyMetadata[F[_, _], G[_, _]] extends ReflectTransformer[F, G] {
51
+ def transformMetadata[K, A](f: F[K, A]): Lazy[G[K, A]]
52
+ }
53
+ ```
54
+
55
+ Each `transformX` implementation `OnlyMetadata` provides is one line: transform the metadata, then rebuild the same node with the same children and shape, swapping in the new metadata. `HasBinding[F]` — the type class that extracts a `Binding[T, A]` out of an arbitrary `F[T, A]` — is itself a `ReflectTransformer.OnlyMetadata[F, Binding]` whose `transformMetadata` calls the one method a `HasBinding[F]` instance supplies.
56
+
57
+ ## Stripping Bindings: `ReflectTransformer.noBinding`
58
+
59
+ `ReflectTransformer.noBinding[F]()` is a predefined, stateless transformer that discards whatever metadata `F` holds and replaces it with the singleton `NoBinding` value, regardless of what `F` was. It is what `Reflect#noBinding` and, through it, `Schema#toDynamicSchema` use to turn a bound, operational schema into a pure-data one safe to serialize:
60
+
61
+ ```scala
62
+ import zio.blocks.schema._
63
+
64
+ case class Person(name: String, age: Int)
65
+ object Person {
66
+ implicit val schema: Schema[Person] = Schema.derived
67
+ }
68
+
69
+ // Reflect.Bound[Person] -> Reflect.Unbound[Person], via ReflectTransformer.noBinding()
70
+ val unbound: Reflect.Unbound[Person] = Person.schema.reflect.noBinding
71
+
72
+ // The public entry point most code uses instead
73
+ val dynamicSchema: DynamicSchema = Person.schema.toDynamicSchema
74
+ ```
75
+
76
+ Because `ReflectTransformer.noBinding` never inspects the metadata it's replacing, one singleton instance (cast per call) serves every `F`, with no allocation per use.
77
+
78
+ ## Attaching Bindings: `RebindTransformer`
79
+
80
+ `RebindTransformer` is the transformer in the opposite direction — `NoBinding` to `Binding` — that [`DynamicSchema#rebind`](./dynamic-schema.md) constructs internally to turn a deserialized, pure-data schema back into an operational one. It is `private[schema]`; the public surface is `DynamicSchema#rebind`, which builds one for you from a [`BindingResolver`](./binding-resolver.md):
81
+
82
+ ```scala
83
+ import zio.blocks.schema._
84
+ import zio.blocks.schema.binding._
85
+
86
+ case class Person(name: String, age: Int)
87
+ object Person {
88
+ implicit val schema: Schema[Person] = Schema.derived
89
+ }
90
+
91
+ val dynamicSchema: DynamicSchema = Person.schema.toDynamicSchema
92
+
93
+ val resolver = BindingResolver.defaults.bind(Binding.of[Person])
94
+ val rebound: Schema[Person] = dynamicSchema.rebind[Person](resolver)
95
+ ```
96
+
97
+ Unlike `ReflectTransformer.noBinding`, `RebindTransformer` implements every `transformX` method individually rather than extending `OnlyMetadata` — each one must call a different resolver lookup (`resolveRecord`, `resolveVariant`, `resolveSeqFor`, `resolveMapFor`, `resolveDynamic`, `resolvePrimitive`, `resolveWrapper`) and fail in a way that names which kind of binding was missing.
98
+
99
+ ### `RebindException`
100
+
101
+ A resolver that's missing a binding for some type in the tree makes `DynamicSchema#rebind` throw `RebindException` rather than return quietly, since a `Schema` with an unresolved node isn't safe to hand back:
102
+
103
+ ```scala
104
+ import zio.blocks.schema._
105
+ import zio.blocks.schema.binding._
106
+
107
+ case class Person(name: String, age: Int)
108
+ object Person {
109
+ implicit val schema: Schema[Person] = Schema.derived
110
+ }
111
+
112
+ val dynamicSchema = Person.schema.toDynamicSchema
113
+
114
+ try dynamicSchema.rebind[Person](BindingResolver.empty)
115
+ catch {
116
+ case e: RebindException =>
117
+ // path: the DynamicOptic where the lookup failed
118
+ // typeId: the missing type
119
+ // expectedKind: "Record", "Variant", "Sequence", "Map", "Dynamic", "Primitive", or "Wrapper"
120
+ println(s"${e.expectedKind} binding missing for ${e.typeId.fullName} at ${e.path}")
121
+ }
122
+ ```
123
+
124
+ ## Writing a Full `ReflectTransformer`: Type Class Derivation
125
+
126
+ `OnlyMetadata` covers every transformer described so far, but the interface exists in full for a reason: automatic type class derivation implements every `transformX` method directly, because deriving an instance for a `Record` needs the already-derived instances of its *fields*, not just a metadata swap. `DerivationBuilder.derive` walks a bound schema with a custom `ReflectTransformer[Binding, BindingInstance[TC, ?, ?]]` that, at each node, either substitutes a user-supplied override or invokes a [`Deriver[TC]`](./type-class-derivation.md) with the node's already-transformed children, then pairs the result with the original binding. This is the pattern to reach for when a transformation genuinely depends on more than the metadata at a single node.
127
+
128
+ ## Cycle Safety and the Transform Cache
129
+
130
+ A recursive schema — one whose `Reflect` tree refers back to itself — reaches that self-reference through a `Reflect.Deferred` node. `Deferred#transform` doesn't call any `transformX` method; it looks up the pair `(this Deferred, this transformer)` in a per-thread memoization map first, and only builds a new deferred node on a miss. This is what makes `Reflect#transform` terminate on a recursive schema, and what lets a `Deferred` reached through multiple paths share one transformed result instead of being rebuilt repeatedly.
131
+
132
+ The two public entry points — `Reflect#noBinding` and `DynamicSchema#rebind` — wrap their call in an internal scope that clears this cache once the outermost `Reflect#transform` finishes, so a transformer's captured state (override maps, resolvers) doesn't stay pinned on the thread once its call returns. Reaching either entry point is enough to get this for free; it isn't something you configure.
133
+
134
+ ## See Also
135
+
136
+ - [Reflect](./reflect.md) — the tree `ReflectTransformer` rewrites, and its eight node kinds
137
+ - [Binding](./binding.md) — what `F`/`G` are in practice: `Binding` and `NoBinding`
138
+ - [BindingResolver](./binding-resolver.md) — the lookup `RebindTransformer` delegates to
139
+ - [DynamicSchema](./dynamic-schema.md) — `Schema#toDynamicSchema` and `DynamicSchema#rebind`, the public entry points
140
+ - [Type Class Derivation](./type-class-derivation.md) — the fullest real use of the interface
@@ -39,13 +39,13 @@ The bidirectional data flow looks like this:
39
39
  `As` is part of `zio-blocks-schema`:
40
40
 
41
41
  ```scala
42
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
42
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.55"
43
43
  ```
44
44
 
45
45
  For Scala.js and Scala Native, use `%%%`:
46
46
 
47
47
  ```scala
48
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
48
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.55"
49
49
  ```
50
50
 
51
51
  Supported Scala versions: 2.13.x and 3.x.
@@ -240,7 +240,7 @@ trait As[A, B] {
240
240
 
241
241
  ```scala
242
242
  val revAs: As[LongBox, IntBox] = boxAs.reverse
243
- // revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@29c128e0
243
+ // revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@50968506
244
244
 
245
245
  revAs.into(LongBox(5L))
246
246
  // res11: Either[SchemaError, IntBox] = Right(IntBox(5))
@@ -302,7 +302,7 @@ We import `As.reverseInto` and use it to obtain the reverse `Into[Int, String]`:
302
302
  import As.reverseInto
303
303
 
304
304
  val intToStr: Into[Int, String] = reverseInto[String, Int]
305
- // intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$19927/0x00007f2996e03990@597c011
305
+ // intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda/0x0000000027f8bc38@794d4993
306
306
  intToStr.into(42)
307
307
  // res14: Either[SchemaError, String] = Right("42")
308
308
  ```
@@ -69,13 +69,13 @@ Compare this to a manual implementation:
69
69
  `Into` is part of the `zio-blocks-schema` module:
70
70
 
71
71
  ```scala
72
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
72
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.55"
73
73
  ```
74
74
 
75
75
  For Scala.js:
76
76
 
77
77
  ```scala
78
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
78
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.55"
79
79
  ```
80
80
 
81
81
  Supported Scala versions: 2.13.x and 3.x.
@@ -76,13 +76,13 @@ val result: Either[OpticCheck, Seq[Boolean]] = combined.eval(alice)
76
76
  ## Installation
77
77
 
78
78
  ```scala
79
- libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.51"
79
+ libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.55"
80
80
  ```
81
81
 
82
82
  For cross-platform (Scala.js):
83
83
 
84
84
  ```scala
85
- libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.51"
85
+ libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.55"
86
86
  ```
87
87
 
88
88
  Supported Scala versions: 2.13.x and 3.x.