@zio.dev/zio-blocks 0.0.51 → 0.0.56

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 (166) 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 -559
  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/endpoint.md +1 -0
  37. package/reference/endpoint/index.md +9 -89
  38. package/reference/endpoint/path-codec.md +12 -24
  39. package/reference/endpoint/route-pattern.md +4 -6
  40. package/reference/endpoint/segment-codec.md +19 -32
  41. package/reference/html.md +313 -9
  42. package/reference/htmx/index.md +4 -52
  43. package/reference/htmx/response-headers.md +240 -0
  44. package/reference/http-model/headers.md +735 -0
  45. package/reference/http-model/index.md +3 -1
  46. package/reference/http-model/model.md +107 -71
  47. package/reference/http-model/schema-codecs.md +522 -0
  48. package/reference/http-model/schema.md +6 -3
  49. package/reference/http-model/server-sent-event.md +341 -0
  50. package/reference/jwt.md +195 -0
  51. package/reference/maybe.md +128 -11
  52. package/reference/media-type.md +2 -2
  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/index.md +1 -1
  57. package/reference/resource-management/resource.md +2 -98
  58. package/reference/resource-management/scope.md +1 -209
  59. package/reference/resource-management/wire.md +4 -50
  60. package/reference/ringbuffer/advanced.mdx +1 -1
  61. package/reference/ringbuffer/index.mdx +3 -3
  62. package/reference/ringbuffer/mpmc.mdx +38 -4
  63. package/reference/ringbuffer/mpsc.mdx +36 -4
  64. package/reference/ringbuffer/spmc.mdx +1 -1
  65. package/reference/ringbuffer/spsc.mdx +87 -15
  66. package/reference/schema/allows.md +0 -96
  67. package/reference/schema/binding.md +2 -2
  68. package/reference/schema/built-in-codecs/avro.md +2 -2
  69. package/reference/schema/built-in-codecs/bson.md +50 -20
  70. package/reference/schema/built-in-codecs/csv.md +2 -2
  71. package/reference/schema/built-in-codecs/index.md +3 -3
  72. package/reference/schema/built-in-codecs/json/index.md +2 -2
  73. package/reference/schema/built-in-codecs/json/json.md +1 -0
  74. package/reference/schema/built-in-codecs/messagepack.md +3 -3
  75. package/reference/schema/built-in-codecs/thrift.md +2 -2
  76. package/reference/schema/built-in-codecs/toon.md +3 -3
  77. package/reference/schema/built-in-codecs/yaml.md +2 -2
  78. package/reference/schema/codec.md +11 -11
  79. package/reference/schema/dynamic-optic.md +48 -3
  80. package/reference/schema/dynamic-schema.md +3 -3
  81. package/reference/schema/index.md +2 -0
  82. package/reference/schema/path-interpolator.md +2 -0
  83. package/reference/schema/reflect-transformer.md +140 -0
  84. package/reference/schema/schema-evolution/as.md +4 -4
  85. package/reference/schema/schema-evolution/into.md +2 -2
  86. package/reference/schema/schema-expr.md +2 -2
  87. package/reference/schema/schema-search.md +263 -0
  88. package/reference/schema/schema.md +10 -2
  89. package/reference/schema/type-class-derivation.md +1 -1
  90. package/reference/smithy.md +502 -3
  91. package/reference/sql/db-codec-deriver.md +3 -3
  92. package/reference/sql/db-codec.md +22 -22
  93. package/reference/sql/db-con.md +4 -4
  94. package/reference/sql/db-connection.md +1 -1
  95. package/reference/sql/db-param.md +1 -1
  96. package/reference/sql/db-result-reader.md +4 -2
  97. package/reference/sql/db-tx.md +46 -14
  98. package/reference/sql/ddl.md +1 -1
  99. package/reference/sql/frag.md +44 -10
  100. package/reference/sql/index.md +7 -7
  101. package/reference/sql/repo.md +15 -15
  102. package/reference/sql/sql-dialect.md +1 -1
  103. package/reference/sql/sql-logger.md +1 -1
  104. package/reference/sql/sql-name-mapper.md +3 -3
  105. package/reference/sql/table-metadata.md +3 -3
  106. package/reference/sql/table.md +10 -10
  107. package/reference/sql/transactor-zio.md +1 -1
  108. package/reference/sql/transactor.md +21 -11
  109. package/reference/sql-zio.md +2 -2
  110. package/reference/streams/core/index.md +32 -0
  111. package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
  112. package/reference/streams/{sink.md → core/sink.md} +331 -353
  113. package/reference/streams/{stream.md → core/stream.md} +919 -209
  114. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  115. package/reference/streams/execution-and-compatibility/index.md +35 -0
  116. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  117. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  118. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  119. package/reference/streams/index.md +140 -67
  120. package/reference/streams/primitives/index.md +30 -0
  121. package/reference/streams/primitives/reader.md +1992 -0
  122. package/reference/streams/{writer.md → primitives/writer.md} +254 -98
  123. package/reference/telemetry/common/any-value.md +90 -0
  124. package/reference/telemetry/common/attribute-key.md +87 -0
  125. package/reference/telemetry/common/attributes.md +118 -0
  126. package/reference/telemetry/common/index.md +39 -0
  127. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  128. package/reference/telemetry/common/resource.md +34 -0
  129. package/reference/telemetry/index.md +311 -0
  130. package/reference/telemetry/logging/index.md +197 -0
  131. package/reference/telemetry/logging/log-enrichment.md +72 -0
  132. package/reference/telemetry/logging/log-formatter.md +100 -0
  133. package/reference/telemetry/logging/log-record-processor.md +56 -0
  134. package/reference/telemetry/logging/log-record.md +44 -0
  135. package/reference/telemetry/logging/log-writer.md +64 -0
  136. package/reference/telemetry/logging/logger-provider.md +142 -0
  137. package/reference/telemetry/logging/logger.md +83 -0
  138. package/reference/telemetry/logging/severity.md +62 -0
  139. package/reference/telemetry/metrics/index.md +150 -0
  140. package/reference/telemetry/metrics/instruments.md +183 -0
  141. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  142. package/reference/telemetry/metrics/meter-provider.md +76 -0
  143. package/reference/telemetry/metrics/meter.md +98 -0
  144. package/reference/telemetry/metrics/metric-data.md +57 -0
  145. package/reference/telemetry/otel/custom-exporter.md +216 -0
  146. package/reference/telemetry/otel/index.md +212 -0
  147. package/reference/telemetry/tracing/index.md +155 -0
  148. package/reference/telemetry/tracing/sampler.md +89 -0
  149. package/reference/telemetry/tracing/span-builder.md +57 -0
  150. package/reference/telemetry/tracing/span-context.md +39 -0
  151. package/reference/telemetry/tracing/span-data.md +32 -0
  152. package/reference/telemetry/tracing/span-kind.md +55 -0
  153. package/reference/telemetry/tracing/span-processor.md +53 -0
  154. package/reference/telemetry/tracing/span-status.md +47 -0
  155. package/reference/telemetry/tracing/span.md +117 -0
  156. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  157. package/reference/telemetry/tracing/tracer.md +52 -0
  158. package/reference/typeid.md +0 -64
  159. package/sidebars.js +365 -185
  160. package/undocumented-report.md +528 -270
  161. package/reference/config.md +0 -158
  162. package/reference/streams/concurrent-operators.md +0 -106
  163. package/reference/streams/reader.md +0 -1284
  164. package/reference/streams/scala-2-compatibility.md +0 -55
  165. package/reference/streams/zero-boxing.md +0 -275
  166. package/reference/telemetry.md +0 -693
@@ -0,0 +1,263 @@
1
+ ---
2
+ id: schema-search
3
+ title: "Schema Search and Update"
4
+ ---
5
+
6
+ `SchemaSearch` and `TypeSearch` are the two `DynamicOptic` node kinds that turn a path into a search: instead of naming one field, they match every value of a shape or type anywhere in a structure. This page covers the machinery behind that search once a path resolves against a value or a schema: `SchemaMatch`, the structural predicate the search matches against; `SearchTraversal`, the `Traversal[S, A]` engine that executes a search over a typed value and lets you fold, modify, or check the matches; and `Updater`, the mechanism for rewriting a `Reflect`/`Term` tree at a resolved path rather than the values it describes.
7
+
8
+ For the search DSLs themselves — the typed `.searchFor[T]` macro, the `#Pattern` string syntax, and the supported pattern grammar — see [DynamicOptic](./dynamic-optic.md#search-optics). This page assumes you can already build a search path and focuses on what runs once you have one.
9
+
10
+ ## Design & Structure
11
+
12
+ A search path splits into two phases that operate on different representations of your data:
13
+
14
+ ```
15
+ Typed value S Schema tree Reflect[F, S]
16
+ │ │
17
+ ▼ ▼
18
+ SearchTraversal[S, A] Reflect#updated(path)(Updater)
19
+ (built from .searchFor[T] (rewrites the Reflect/Term
20
+ or SearchTraversal.apply) node found at a path)
21
+ │
22
+ ▼
23
+ DynamicValue tree, walked depth-first
24
+ │
25
+ ▼
26
+ SchemaMatch.matches(pattern, value) ← only for #Pattern searches;
27
+ (the structural predicate) .searchFor[T] matches by TypeId instead
28
+ ```
29
+
30
+ `SearchTraversal` finds and transforms **values** — it decodes candidate subtrees of a concrete `S` and collects the ones that decode as `A`. `Updater` rewrites **schema metadata** — it walks the `Reflect`/`Term` tree that describes a type and replaces the node at a path, independent of any particular value. The two are easy to conflate because both start from a `DynamicOptic` path, but a `TypeSearch`/`SchemaSearch` node is only meaningful to the value-searching side: `Reflect#updated` does not special-case those nodes, so a path that reaches `Reflect#updated`/`Schema#updated` must resolve through `Field`/`Case`/`AtIndex`/`Elements`/`Wrapped`/`MapKeys`/`MapValues` nodes only.
31
+
32
+ ## SchemaMatch — Structural Pattern Matching
33
+
34
+ `SchemaMatch.matches` is the predicate a `#Pattern` search node matches against once it reaches a `DynamicValue`: given a `SchemaRepr` pattern and a `DynamicValue`, it returns whether the value has that shape:
35
+
36
+ ```scala
37
+ object SchemaMatch {
38
+ def matches(pattern: SchemaRepr, value: DynamicValue): Boolean
39
+ }
40
+ ```
41
+
42
+ `SchemaRepr` is the small pattern language `#Pattern` strings parse into — `Wildcard`, `Primitive(name)`, `Record(fields)`, `Variant(cases)`, `Sequence(element)`, `Map(key, value)`, `Optional(inner)`, and `Nominal(name)`. Constructing one directly and matching it against a `DynamicValue` shows the same rules the `#record { ... }` syntax compiles down to:
43
+
44
+ ```scala
45
+ import zio.blocks.schema._
46
+
47
+ val personPattern = SchemaRepr.Record(
48
+ IndexedSeq("name" -> SchemaRepr.Primitive("string"), "age" -> SchemaRepr.Primitive("int"))
49
+ )
50
+
51
+ val alice = DynamicValue.Record("name" -> DynamicValue.string("Alice"), "age" -> DynamicValue.int(30))
52
+ val bob = DynamicValue.Record("name" -> DynamicValue.string("Bob"), "role" -> DynamicValue.string("admin"))
53
+ ```
54
+
55
+ `Record` matching is a subset match — every field named in the pattern must exist with a matching type, but extra fields on the value are fine, which is why `bob` fails only because `age` is missing, not because of the extra `role` field:
56
+
57
+ ```scala
58
+ SchemaMatch.matches(personPattern, alice)
59
+ // res0: Boolean = true
60
+ SchemaMatch.matches(personPattern, bob)
61
+ // res1: Boolean = false
62
+ ```
63
+
64
+ `Wildcard` matches anything, and `Sequence`/`Map` patterns match when every element or entry matches — an empty sequence or map always matches, since there is nothing to fail on:
65
+
66
+ ```scala
67
+ import zio.blocks.schema._
68
+
69
+ val ages = DynamicValue.Sequence(DynamicValue.int(1), DynamicValue.int(2))
70
+ SchemaMatch.matches(SchemaRepr.Sequence(SchemaRepr.Primitive("int")), ages)
71
+ SchemaMatch.matches(SchemaRepr.Wildcard, ages)
72
+ ```
73
+
74
+ `Nominal(name)` always returns `false` against a `DynamicValue`, because a decoded value carries no record of the Scala type it came from — the same limitation documented for `#Person`-style patterns in [DynamicOptic's known limitation](./dynamic-optic.md#known-limitation-nominal-matching-in-untyped-contexts). Matching by nominal type requires the typed `.searchFor[T]` API instead, which matches by `TypeId` rather than by structure.
75
+
76
+ ## SearchTraversal — Executing a Search Over Values
77
+
78
+ `SearchTraversal` is the `Traversal[S, A]` that `.searchFor[T]` and `#Pattern` searches compile down to. Construct one directly from a pair of schemas when you want the traversal without going through the macro or the path interpolator:
79
+
80
+ ```scala
81
+ import zio.blocks.schema._
82
+
83
+ case class Address(city: String)
84
+ object Address {
85
+ implicit val schema: Schema[Address] = Schema.derived
86
+ }
87
+
88
+ case class Person(name: String, age: Int, address: Address)
89
+ object Person {
90
+ implicit val schema: Schema[Person] = Schema.derived
91
+ }
92
+
93
+ case class Team(name: String, lead: Person, members: List[Person])
94
+ object Team {
95
+ implicit val schema: Schema[Team] = Schema.derived
96
+ }
97
+
98
+ case class Company(name: String, ceo: Person, teams: List[Team])
99
+ object Company extends CompanionOptics[Company] {
100
+ implicit val schema: Schema[Company] = Schema.derived
101
+ }
102
+
103
+ val findPeople: Traversal[Company, Person] = SearchTraversal[Company, Person]
104
+
105
+ val acme = Company(
106
+ "Acme",
107
+ ceo = Person("Alice", 45, Address("NYC")),
108
+ teams = List(
109
+ Team("Platform", Person("Bob", 32, Address("SF")), List(Person("Carol", 28, Address("SF")))),
110
+ Team("Sales", Person("Dave", 39, Address("LA")), Nil)
111
+ )
112
+ )
113
+ ```
114
+
115
+ `SearchTraversal[Company, Person]` resolves the two implicit `Schema` instances the same way `Company.optic(_.searchFor[Person])` does—both produce the identical traversal, so pick whichever reads better at the call site. `Traversal#fold` walks every match depth-first, left-to-right, which is why the CEO comes before any team, and each team's lead comes before its members:
116
+
117
+ ```scala
118
+ findPeople.fold(acme)(List.empty[String], (names, p) => names :+ p.name)
119
+ // res3: List[String] = List("Alice", "Bob", "Carol", "Dave")
120
+ ```
121
+
122
+ `Traversal#modify` rewrites every match in place and rebuilds the structure around it, `modifyOption` returns `None` instead of a no-op result when nothing matched, and `modifyOrFail` surfaces a decoding failure as `Left` instead of silently keeping the original value:
123
+
124
+ ```scala
125
+ findPeople.modify(acme, p => p.copy(age = p.age + 1)).ceo.age
126
+ // res4: Int = 46
127
+ SearchTraversal[Company, Address].modifyOption(acme, a => a.copy(city = a.city.toUpperCase)).map(_.ceo.address.city)
128
+ // res5: Option[String] = Some("NYC")
129
+ SearchTraversal[Team, Address].modifyOption(acme.teams.head, a => a).isDefined
130
+ // res6: Boolean = true
131
+ ```
132
+
133
+ `Traversal#check` reports whether a traversal has at least one match, which is how a search-backed traversal signals "nothing here" without throwing:
134
+
135
+ ```scala
136
+ SearchTraversal[Company, Person].check(acme).isEmpty
137
+ // res7: Boolean = true
138
+ SearchTraversal[Team, Boolean].check(acme.teams.head).isEmpty
139
+ // res8: Boolean = false
140
+ ```
141
+
142
+ ### Composing a Search With Other Optics
143
+
144
+ A search traversal composes with `Lens`, `Prism`, `Optional`, and other `Traversal`s on either side, so you can narrow the search to part of a structure or refine each match further. Searching from a specific field finds only what is reachable from there, and appending a lens after a search projects each match down to one of its fields:
145
+
146
+ ```scala
147
+ import zio.blocks.schema._
148
+
149
+ object CompanyOptics extends CompanionOptics[Company] {
150
+ implicit val schema: Schema[Company] = Company.schema
151
+
152
+ // Search restricted to one team: only Bob and Carol, never Alice or Dave
153
+ val platformMembers: Traversal[Company, Person] = optic(_.teams.at(0).searchFor[Person])
154
+
155
+ // Search first, then focus each match's city
156
+ val allCities: Traversal[Company, String] = optic(_.searchFor[Address].city)
157
+ }
158
+ ```
159
+
160
+ Both directions rebuild the same way a plain path-based traversal does: `Traversal#modify`, `Traversal#fold`, and `Traversal#check` all work on the composed traversal exactly as they do on a bare `SearchTraversal`, because composition produces another `Traversal[S, A]` — the fact that a search sits inside it is an implementation detail, not a different API.
161
+
162
+ ### Recursive Types Are Safe to Search
163
+
164
+ Because `SearchTraversal` walks the decoded `DynamicValue` of a concrete value rather than the schema definition, searching a recursive type (a tree, a linked structure) terminates naturally — the value itself is always finite, even though its `Reflect` describes an unbounded type. There is nothing extra to opt into; a self-referential case class searches the same way a flat one does.
165
+
166
+ ## Updater — Rewriting Schema Metadata
167
+
168
+ `Reflect.Updater` and `Term.Updater` are the callbacks behind `Schema#updated` and `Reflect#updated` — the mechanism for rewriting a schema's metadata (documentation, defaults, validations, or a field's shape entirely) at a resolved path, as opposed to rewriting the values that schema describes:
169
+
170
+ ```scala
171
+ object Reflect {
172
+ trait Updater[F[_, _]] {
173
+ def update[A](reflect: Reflect[F, A]): Reflect[F, A]
174
+ }
175
+ }
176
+
177
+ object Term {
178
+ trait Updater[F[_, _]] {
179
+ def update[S, A](input: Term[F, S, A]): Option[Term[F, S, A]]
180
+ }
181
+ }
182
+ ```
183
+
184
+ The two differ in one important way: `Reflect.Updater#update` is total — it always returns a `Reflect`, because a schema node can be re-shaped but not removed. `Term.Updater#update` is partial — returning `None` deletes the field or case the updater targets, which is how `Record#modifyField` and `Variant#modifyCase` support dropping a member rather than only renaming or retyping it.
185
+
186
+ [Schema](./schema.md#updating-nested-schemas) already covers the common case, updating one field through an optic with a plain function: `Schema[Person].updated(Person.address)(_.doc("Mailing address"))`. That convenience overload builds a `Reflect.Updater` for you. Reaching for `Reflect.Updater` directly is what you need for the case that overload cannot express — a `DynamicOptic` path built at runtime, or a rewrite that needs the full node rather than just its focus:
187
+
188
+ ```scala
189
+ import zio.blocks.schema._
190
+ import zio.blocks.schema.binding.Binding
191
+
192
+ case class Config(host: String, port: Int)
193
+ object Config {
194
+ implicit val schema: Schema[Config] = Schema.derived
195
+ }
196
+
197
+ // Attach documentation to a field found by a runtime-built DynamicOptic path
198
+ val documented: Option[Schema[Config]] =
199
+ Schema[Config].updated(DynamicOptic.root.field("port"))(new Reflect.Updater[Binding] {
200
+ def update[A](reflect: Reflect[Binding, A]): Reflect[Binding, A] =
201
+ reflect.doc("The TCP port the server listens on")
202
+ })
203
+ ```
204
+
205
+ `Term.Updater` operates one level up, on the named field or case itself rather than its value, which is what makes rename and delete possible. Renaming reuses the term's existing `value`; deleting a field returns `None` and the field disappears from the record entirely:
206
+
207
+ ```scala
208
+ import zio.blocks.schema._
209
+ import zio.blocks.schema.binding.Binding
210
+
211
+ case class LegacyUser(id: Long, username: String, internalNotes: String)
212
+ object LegacyUser {
213
+ implicit val schema: Schema[LegacyUser] = Schema.derived
214
+ }
215
+
216
+ val userRecord = Schema[LegacyUser].reflect.asRecord.get
217
+
218
+ // Rename username -> name
219
+ val renamed = userRecord.modifyField("username")(new Term.Updater[Binding] {
220
+ def update[S, A](input: Term[Binding, S, A]): Option[Term[Binding, S, A]] =
221
+ Some(input.copy(name = "name"))
222
+ })
223
+
224
+ // Drop internalNotes entirely by returning None
225
+ val withoutNotes = userRecord.modifyField("internalNotes")(new Term.Updater[Binding] {
226
+ def update[S, A](input: Term[Binding, S, A]): Option[Term[Binding, S, A]] = None
227
+ })
228
+ ```
229
+
230
+ Both updaters ran against the same `userRecord` independently, so `renamed` still has three fields with one renamed, while `withoutNotes` has two:
231
+
232
+ ```scala
233
+ renamed.map(_.fields.map(_.name))
234
+ // res11: Option[IndexedSeq[String]] = Some(
235
+ // Vector("id", "name", "internalNotes")
236
+ // )
237
+ withoutNotes.map(_.fields.map(_.name))
238
+ // res12: Option[IndexedSeq[String]] = None
239
+ ```
240
+
241
+ :::warning[Search nodes are not valid `updated` paths]
242
+ `Reflect#updated`/`Schema#updated` walk a `DynamicOptic` through `Field`, `Case`, `AtIndex`/`AtIndices`/`Elements`, `Wrapped`, and `MapKeys`/`MapValues` nodes only. A path containing a `TypeSearch` or `SchemaSearch` node is not rejected outright, but it is not handled either — it falls into the same branch as `MapKeys`/`MapValues` and produces an unspecified result. Build search-based rewrites with `SearchTraversal#modify` on a value instead of `Schema#updated` on a schema. `DynamicMigration`, by contrast, rejects search nodes outright with an explicit error, since a migration requires one statically-known path.
243
+ :::
244
+
245
+ ## Where Search Nodes Are (and Aren't) Handled
246
+
247
+ The same `TypeSearch`/`SchemaSearch` node means different things depending on which API resolves the path it's part of:
248
+
249
+ | API | Search nodes |
250
+ | -------------------------- | ---------------------------------------------------------- |
251
+ | `SearchTraversal` / `.searchFor[T]` / `#Pattern` on a value | The intended use — this is what builds and executes the search |
252
+ | `Reflect#get` / `Schema#get` | Supported — resolves the first match, then continues the remaining path from there |
253
+ | `Json` / `DynamicValue` patch paths (`JsonPatch`) | Supported — rewrites every match, not just the first |
254
+ | `Reflect#updated` / `Schema#updated` | Not handled — falls through to the map-key/value branch; use `SearchTraversal#modify` instead |
255
+ | `DynamicMigration` | Rejected outright with `"Type/Schema search nodes are not supported in migration paths"` |
256
+
257
+ ## See Also
258
+
259
+ - [DynamicOptic](./dynamic-optic.md#search-optics) — the `.searchFor[T]` and `#Pattern` search DSLs, and the full pattern grammar table.
260
+ - [Path Interpolator](./path-interpolator.md) — the `p"..."` string syntax that `#Pattern` search nodes parse from.
261
+ - [Schema](./schema.md#updating-nested-schemas) — `Schema#updated` and `Schema#@@` for the common, optic-based case of rewriting one field's metadata.
262
+ - [Reflect](./reflect.md) — the node types (`Record`, `Variant`, `Sequence`, `Map`, `Wrapper`, `Deferred`) that `Updater` rewrites and `SearchTraversal` decodes against.
263
+ - [Optics](./optics.md) — the base `Traversal[S, A]` type that `SearchTraversal` implements and composes with.
@@ -522,6 +522,8 @@ val updated: Option[Schema[Person]] = Schema[Person]
522
522
  .updated(Person.address)(_.doc("Mailing address"))
523
523
  ```
524
524
 
525
+ For the lower-level `Reflect.Updater`/`Term.Updater` callbacks this method builds on — including how `Term.Updater` can rename or delete a field by returning `None` — see [Schema Search and Update](./schema-search.md#updater--rewriting-schema-metadata).
526
+
525
527
  ## Schema Aspects
526
528
 
527
529
  Schema aspects are a powerful mechanism in ZIO Blocks for transforming schemas. You can think of the schema aspect as a function that takes a reflect and produces a new reflect:
@@ -529,17 +531,21 @@ Schema aspects are a powerful mechanism in ZIO Blocks for transforming schemas.
529
531
  ```scala
530
532
  trait SchemaAspect[-Upper, +Lower, F[_, _]] {
531
533
  def apply[A >: Lower <: Upper](reflect: Reflect[F, A]): Reflect[F, A]
532
- def recursive(implicit ev1: Any <:< Upper, ev2: Lower <:< Nothing): SchemaAspect[Upper, Lower, F]
533
534
  }
534
535
  ```
535
536
 
536
- The `Schema` data type has a `@@` method used for applying schema aspects:
537
+ The `Schema` data type has a `@@` method that applies schema aspects, and it's a thin wrapper over two `Reflect#aspect` overloads: applying an aspect to a whole schema just calls `aspect(reflect)` directly, and applying one at a path uses [`Reflect#updated`](./reflect-transformer.md) to rewrite the subtree the optic points to:
537
538
 
538
539
  ```scala
539
540
  case class Schema[A](reflect: Reflect.Bound[A]) {
540
541
  def @@[Min >: A, Max <: A](aspect: SchemaAspect[Min, Max, Binding]): Schema[A] = ???
541
542
  def @@[B](part: Optic[A, B], aspect: SchemaAspect[B, B, Binding]) = ???
542
543
  }
544
+
545
+ sealed trait Reflect[F[_, _], A] {
546
+ def aspect[Min >: A, Max <: A](aspect: SchemaAspect[Min, Max, F]): Reflect[F, A]
547
+ def aspect[B, Min >: B, Max <: B](optic: Optic[A, B], aspect: SchemaAspect[Min, Max, F]): Reflect[F, A]
548
+ }
543
549
  ```
544
550
 
545
551
  These methods enable us to use `@@` syntax for applying aspects to either the entire schema or a specific path within the schema using optics:
@@ -563,6 +569,8 @@ Currently, ZIO Blocks provides the following built-in schema aspects:
563
569
  - `SchemaAspect.doc`: Attach documentation to schema or field
564
570
  - `SchemaAspect.examples`: Attach example values to schema or field
565
571
 
572
+ The path-targeted overload doesn't fail on an optic that doesn't resolve against the schema — it falls back to the original schema unchanged, the same behavior [`Reflect#updated`](./reflect-transformer.md) has when a path finds nothing. This matters most when an optic is built by hand rather than through the `optic(_.field)` macro: a lens pointing at a field name that isn't actually on the record silently leaves the schema untouched rather than throwing.
573
+
566
574
  ## Modifiers
567
575
 
568
576
  Modifiers in ZIO Blocks provide a mechanism to attach metadata and configuration to schema elements without polluting the domain types themselves. They serve as the successor to ZIO Schema 1's annotation system, with the critical advantage of being **pure data** so, unlike Scala annotations, modifiers are runtime values that can be serialized.
@@ -1517,7 +1517,7 @@ Now we can use the derived `Gen[Person]` instance to generate random `Person` va
1517
1517
 
1518
1518
  ```scala
1519
1519
  val random = new Random(42) // Seeded for reproducible output
1520
- // random: Random = scala.util.Random@d903a4c
1520
+ // random: Random = scala.util.Random@362685dd
1521
1521
 
1522
1522
  Person.gen.generate(random)
1523
1523
  // res14: Person = Person(name = "p", age = -1360544799)