@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,336 @@
1
+ ---
2
+ id: rollout
3
+ title: "Rollout"
4
+ sidebar_label: "Rollout"
5
+ ---
6
+
7
+ `Rollout` is the expression language behind dynamic flags. An expression is a semicolon-separated list of choices, each optionally targeted at a slash-separated path pattern and a percentage bucket. `Rollout` parses expressions into `Rollout.Choices`, evaluates them against a path and a bucket, and computes the deterministic bucket for a key. Supporting types: `Rollout.Choice`, `Rollout.Selector`, `Rollout.Segment`, `Flag.ReloadResult`, `DynamicFlag.UpdateRecord`. The parsed form of an expression:
8
+
9
+ ```scala
10
+ object Rollout {
11
+ final case class Choices(entries: List[Choice])
12
+
13
+ sealed trait Choice
14
+ object Choice {
15
+ final case class Targeted(value: String, selector: Selector) extends Choice
16
+ final case class CatchAll(value: String) extends Choice
17
+ }
18
+
19
+ final case class Selector(segments: List[Segment], percentage: Maybe[Int])
20
+
21
+ sealed trait Segment
22
+ object Segment {
23
+ case object Wildcard extends Segment
24
+ final case class Literal(value: String) extends Segment
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Motivation
30
+
31
+ A feature flag that is only on or off cannot express the thing teams actually want: on for 10% of users, on for everyone in one region, off everywhere else. Encoding that as three separate booleans means three flags to keep consistent, and encoding it as code means a deploy for every adjustment.
32
+
33
+ A rollout expression puts the whole decision in one string, which is what makes it deployable through a config source. `"true@*/eu/100%; true@*/50%; false"` reads as three rules in priority order: everyone in `eu`, then half of everyone else, then off. Changing the rollout is changing that string.
34
+
35
+ The percentage is a function of the key, not a coin flip. The same key always lands in the same bucket, so a user who sees the new checkout flow sees it on every request, and raising 50% to 60% keeps the original 50% inside the group rather than reshuffling everyone.
36
+
37
+ ## Grammar
38
+
39
+ An expression is a list of choices; a targeted choice pairs a value with a selector; a selector is a path pattern with an optional percentage:
40
+
41
+ ```
42
+ expression = choice { ";" choice }
43
+ choice = value "@" selector | value
44
+ selector = path [ "/" percentage ]
45
+ path = segment { "/" segment }
46
+ segment = "*" | literal
47
+ percentage = digits "%"
48
+ ```
49
+
50
+ Whitespace around choices and segments is trimmed. A choice with no `@` is a catch-all that matches everything.
51
+
52
+ The percentage is always the final slash-separated component, which is why `prod/50%` means "path `prod`, 50 percent" rather than a two-segment path. A selector consisting only of a percentage is rejected — there is no implicit "match everything" path, so write `*/50%` when you mean half of all keys.
53
+
54
+ ## Evaluation
55
+
56
+ Evaluation walks the choices left to right and returns the first match. A catch-all matches immediately, so anything after it is unreachable.
57
+
58
+ ### Path Matching
59
+
60
+ A path matches a selector only when both have the **same number of segments**, compared position by position. `Segment.Wildcard` matches any single segment; `Segment.Literal` requires equality:
61
+
62
+ ```scala
63
+ import zio.blocks.config._
64
+
65
+ val bucket = Rollout.bucketFor("user-1234")
66
+ ```
67
+
68
+ A single-literal selector matches only that exact one-segment path:
69
+
70
+ ```scala
71
+ Rollout.select("on@beta; off", "beta", bucket)
72
+ // res0: Maybe[String] = "on"
73
+ Rollout.select("on@beta; off", "prod", bucket)
74
+ // res1: Maybe[String] = "off"
75
+ ```
76
+
77
+ A wildcard matches any one segment, but still only one:
78
+
79
+ ```scala
80
+ Rollout.select("on@*; off", "anything", bucket)
81
+ // res2: Maybe[String] = "on"
82
+ Rollout.select("on@*; off", "two/segments", bucket)
83
+ // res3: Maybe[String] = "off"
84
+ ```
85
+
86
+ Multi-segment patterns mix the two, matching a path of exactly that length:
87
+
88
+ ```scala
89
+ Rollout.select("on@*/eu; off", "user-1234/eu", bucket)
90
+ // res4: Maybe[String] = "on"
91
+ Rollout.select("on@*/eu; off", "user-1234/us", bucket)
92
+ // res5: Maybe[String] = "off"
93
+ ```
94
+
95
+ :::warning[Segment count is exact]
96
+ There is no prefix or suffix matching and no multi-segment wildcard. A selector with two segments never matches a three-segment path. This is the most common cause of a rollout that silently falls through to its catch-all.
97
+ :::
98
+
99
+ ### Percentages
100
+
101
+ The percentage is compared against the bucket, with three values special-cased: an absent percentage always matches a matching path, `0%` never matches, and `100%` always matches. Otherwise the choice matches when `bucket < percentage`:
102
+
103
+ | Percentage | Matches when the path matches |
104
+ | ---------- | ------------------------------- |
105
+ | *(absent)* | Always |
106
+ | `0%` | Never |
107
+ | `100%` | Always |
108
+ | `n%` | `bucket < n` |
109
+
110
+ Because the comparison is a strict less-than against a bucket in `0..99`, a percentage behaves as the literal share of keys it names.
111
+
112
+ ### Bucketing
113
+
114
+ `Rollout.bucketFor` hashes a key with MurmurHash3 and reduces it to `0..99`. The result is stable for a given key across processes and restarts:
115
+
116
+ ```scala
117
+ Rollout.bucketFor("user-1234")
118
+ // res6: Int = 7
119
+ Rollout.bucketFor("user-1234") == Rollout.bucketFor("user-1234")
120
+ // res7: Boolean = true
121
+ ```
122
+
123
+ Different keys spread across the range, which is what makes a percentage approximate a uniform share:
124
+
125
+ ```scala
126
+ (1 to 5).map(i => Rollout.bucketFor(s"user-$i"))
127
+ // res8: IndexedSeq[Int] = Vector(64, 97, 86, 68, 0)
128
+ ```
129
+
130
+ Stability is the reason to pass a user or session identifier as the key rather than something that changes per call. A random key would re-roll the dice on every evaluation.
131
+
132
+ ## API
133
+
134
+ Five methods cover parsing, evaluation, and diagnostics. `Rollout.select` is the one-shot form; the others let you parse once and evaluate many times.
135
+
136
+ ### Rollout.select
137
+
138
+ `Rollout.select` parses and evaluates in one call, returning `Maybe.absent` both when nothing matches and when the expression will not parse:
139
+
140
+ ```scala
141
+ Rollout.select("on@prod/50%; off", "prod", 10)
142
+ // res9: Maybe[String] = "on"
143
+ Rollout.select("@@@not-valid", "prod", 10)
144
+ // res10: Maybe[String] = zio.blocks.maybe.Absent$@53f42f8c
145
+ ```
146
+
147
+ Because a parse failure is indistinguishable from a non-match, use `Rollout.select` only where a malformed expression should behave as "no decision". Parse explicitly when you need to know.
148
+
149
+ ### Rollout.parseChoices
150
+
151
+ `Rollout.parseChoices` returns the parsed `Choices` or the first `ConfigError` it encountered:
152
+
153
+ ```scala
154
+ Rollout.parseChoices("on@prod/50%; off")
155
+ // res11: Either[ConfigError, Choices] = Right(
156
+ // Choices(
157
+ // List(
158
+ // Targeted(
159
+ // value = "on",
160
+ // selector = Selector(segments = List(Literal("prod")), percentage = 50)
161
+ // ),
162
+ // CatchAll("off")
163
+ // )
164
+ // )
165
+ // )
166
+ ```
167
+
168
+ An expression with a malformed choice reports the failure rather than skipping it:
169
+
170
+ ```scala
171
+ Rollout.parseChoices("on@prod/150%")
172
+ // res12: Either[ConfigError, Choices] = Left(
173
+ // InvalidValue(
174
+ // path = "rollout",
175
+ // value = "prod/150%",
176
+ // expectedType = "percentage <= 100",
177
+ // source = "rollout",
178
+ // cause = None
179
+ // )
180
+ // )
181
+ ```
182
+
183
+ Parsing is what `DynamicFlag` does at initialization and on every update, so these are the errors a flag rejects.
184
+
185
+ ### Rollout.evaluateIndex
186
+
187
+ `Rollout.evaluateIndex` evaluates already-parsed choices, which is what the flag does on the hot path — parsing happens once per update, not once per evaluation:
188
+
189
+ ```scala
190
+ val choices = Rollout.parseChoices("on@*/eu; off").toOption.get
191
+ ```
192
+
193
+ Evaluating the same choices for several paths reuses the parse:
194
+
195
+ ```scala
196
+ Rollout.evaluateIndex(choices, "user-1/eu", 42)
197
+ // res13: Maybe[String] = "on"
198
+ Rollout.evaluateIndex(choices, "user-1/us", 42)
199
+ // res14: Maybe[String] = "off"
200
+ ```
201
+
202
+ ### Rollout.validate
203
+
204
+ `Rollout.validate` parses an expression and returns warnings about rules that are suspicious rather than invalid. It flags choices rendered unreachable by an earlier catch-all:
205
+
206
+ ```scala
207
+ Rollout.validate("off; on@prod")
208
+ // res15: Either[ConfigError, List[String]] = Right(
209
+ // List("Choices after index 0 are unreachable (catch-all found)")
210
+ // )
211
+ ```
212
+
213
+ It also flags a pattern whose percentages sum above 100, which usually means someone added a rule instead of adjusting one:
214
+
215
+ ```scala
216
+ Rollout.validate("a@prod/70%; b@prod/60%")
217
+ // res16: Either[ConfigError, List[String]] = Right(
218
+ // List("Cumulative percentage for pattern 'prod' is 130% (exceeds 100%)")
219
+ // )
220
+ ```
221
+
222
+ A clean expression returns an empty list:
223
+
224
+ ```scala
225
+ Rollout.validate("on@*/eu; on@*/50%; off")
226
+ // res17: Either[ConfigError, List[String]] = Right(List())
227
+ ```
228
+
229
+ Warnings are advisory — `DynamicFlag` does not run them. Call `Rollout.validate` in a test or an admin endpoint that accepts expressions from operators.
230
+
231
+ ### Parse Failures
232
+
233
+ Every parse failure is a `ConfigError.InvalidValue` with path and source `"rollout"`, and an `expectedType` describing what was wanted:
234
+
235
+ | Expression | Rejected because |
236
+ | ----------------- | ---------------------------------------------------- |
237
+ | `""` | Empty expression. |
238
+ | `"@prod"` | No value before `@`. |
239
+ | `"on@"` | No selector after `@`. |
240
+ | `"on@50%"` | Percentage with no path; write `on@*/50%`. |
241
+ | `"on@prod/"` | Trailing slash with no percentage. |
242
+ | `"on@prod/abc%"` | Non-numeric percentage. |
243
+ | `"on@prod/150%"` | Percentage above 100. |
244
+
245
+ ## The Parsed Form
246
+
247
+ `Rollout.Choices` wraps an ordered `List[Choice]`, and the order is the evaluation order. Matching on the ADT is how an admin tool can render or rewrite an expression rather than treating it as opaque text:
248
+
249
+ ```scala
250
+ import zio.blocks.config._
251
+
252
+ val parsed = Rollout.parseChoices("on@*/eu/25%; off").toOption.get
253
+ ```
254
+
255
+ A `Choice.Targeted` carries its value and selector; a `Choice.CatchAll` carries only a value:
256
+
257
+ ```scala
258
+ parsed.entries
259
+ // res19: List[Choice] = List(
260
+ // Targeted(
261
+ // value = "on",
262
+ // selector = Selector(
263
+ // segments = List(Wildcard, Literal("eu")),
264
+ // percentage = 25
265
+ // )
266
+ // ),
267
+ // CatchAll("off")
268
+ // )
269
+ ```
270
+
271
+ `Selector#segments` is the path pattern as a list, and `Selector#percentage` is a `Maybe[Int]` that is absent when the selector had no percentage:
272
+
273
+ ```scala
274
+ parsed.entries.collect { case t: Rollout.Choice.Targeted => (t.selector.segments, t.selector.percentage) }
275
+ // res20: List[Tuple2[List[Segment], Maybe[Int]]] = List(
276
+ // (List(Wildcard, Literal("eu")), 25)
277
+ // )
278
+ ```
279
+
280
+ ## Reload Lifecycle
281
+
282
+ A dynamic flag's expression can change while the process runs, either from code or from a source. Both paths record what happened so the change is auditable.
283
+
284
+ ### Flag.ReloadResult
285
+
286
+ `DynamicFlag#reload` re-reads the expression from `FlagSource.Registry` and reports the outcome:
287
+
288
+ | Result | Meaning |
289
+ | ----------------------------------- | ------------------------------------------------------------- |
290
+ | `Flag.ReloadResult.NoSource` | No registered source provides this flag's name; nothing changed. |
291
+ | `Flag.ReloadResult.Unchanged` | The source's expression is identical to the current one. |
292
+ | `Flag.ReloadResult.Updated(old, new)` | The expression was replaced; both versions are reported. |
293
+ | `Flag.ReloadResult.Failed(error)` | The source's expression will not parse; the old one is kept. |
294
+
295
+ `Failed` keeping the previous expression is deliberate: a bad push to a flag service degrades to "no change", not to the constructor default.
296
+
297
+ Reloading is manual. Nothing in the module polls, so drive it from whatever scheduler your application already has:
298
+
299
+ ```scala
300
+ import zio.blocks.config._
301
+
302
+ object newCheckout extends DynamicFlag[Boolean](false, "false")
303
+
304
+ newCheckout.reload() match {
305
+ case Flag.ReloadResult.Updated(from, to) => println(s"rollout changed: $from -> $to")
306
+ case Flag.ReloadResult.Failed(error) => println(s"rollout rejected: ${error.message}")
307
+ case Flag.ReloadResult.Unchanged => ()
308
+ case Flag.ReloadResult.NoSource => println("no source registered for this flag")
309
+ }
310
+ ```
311
+
312
+ ### DynamicFlag.UpdateRecord
313
+
314
+ Every successful change — from `DynamicFlag#update` or from a reload — appends a `DynamicFlag.UpdateRecord` holding the old expression, the new one, and a millisecond timestamp:
315
+
316
+ ```scala
317
+ final case class UpdateRecord(oldExpression: String, newExpression: String, timestampMillis: Long)
318
+ ```
319
+
320
+ `DynamicFlag#updateHistory` returns them most-recent-first, capped at the last ten. Older records are discarded, so the history answers "what changed just now?" rather than serving as an audit log.
321
+
322
+ ### Evaluation Counters
323
+
324
+ `DynamicFlag#apply` increments a per-key counter, and `DynamicFlag#counters` returns a snapshot as a `Map[String, Long]`. The counters are thread-safe but approximate — they use `LongAdder`, so a snapshot taken during traffic may miss in-flight increments.
325
+
326
+ Distinct keys are capped at 100. Once that many have been seen, every new key is counted under the single bucket `"other"` instead of getting its own entry. No exception is thrown and existing counters keep working, so a flag keyed by user id degrades to "100 known users plus a bulk count" rather than leaking memory.
327
+
328
+ `DynamicFlag#evaluate` bypasses counting entirely. Use it on paths where you do not want the bookkeeping, and reserve `DynamicFlag#apply` for the call sites whose distribution you want to observe.
329
+
330
+ `DynamicFlag#parseErrorCount` counts evaluations where a matched value could not be parsed into the flag's type, causing a fall back to the default. A non-zero count is always a misconfigured expression: the rollout matched, but the value it selected was not readable. Because every call still returns the default, this is otherwise silent.
331
+
332
+ ## Integration Points
333
+
334
+ `Rollout` depends only on `Maybe` and `ConfigError`. It is used by `DynamicFlag` for initialization, updates, reloads, and every evaluation, and it can be used directly wherever a path-and-percentage decision is needed without a flag object.
335
+
336
+ See [Flags](./flags.md) for the flag types that drive it, and [Errors](./errors.md) for the error type its parser returns.
@@ -116,7 +116,7 @@ val config = ctx.get[Config] // Compile-time proof it exists
116
116
  Add the ZIO Blocks Context module to your `build.sbt`:
117
117
 
118
118
  ```scala
119
- libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.51"
119
+ libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.56"
120
120
  ```
121
121
 
122
122
  ## Construction
@@ -525,22 +525,6 @@ cd zio-blocks
525
525
  **Context construction: creating contexts with apply, empty.add, and inspecting size/isEmpty/nonEmpty**
526
526
 
527
527
  ```scala title="schema-examples/src/main/scala/context/ContextConstructionExample.scala"
528
- /*
529
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
530
- *
531
- * Licensed under the Apache License, Version 2.0 (the "License");
532
- * you may not use this file except in compliance with the License.
533
- * You may obtain a copy of the License at
534
- *
535
- * http://www.apache.org/licenses/LICENSE-2.0
536
- *
537
- * Unless required by applicable law or agreed to in writing, software
538
- * distributed under the License is distributed on an "AS IS" BASIS,
539
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
540
- * See the License for the specific language governing permissions and
541
- * limitations under the License.
542
- */
543
-
544
528
  package context
545
529
 
546
530
  import zio.blocks.context._
@@ -602,22 +586,6 @@ sbt "schema-examples/runMain context.ContextConstructionExample"
602
586
  **Context retrieval: using get, supertype lookups, and getOption for safe access**
603
587
 
604
588
  ```scala title="schema-examples/src/main/scala/context/ContextRetrievalExample.scala"
605
- /*
606
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
607
- *
608
- * Licensed under the Apache License, Version 2.0 (the "License");
609
- * you may not use this file except in compliance with the License.
610
- * You may obtain a copy of the License at
611
- *
612
- * http://www.apache.org/licenses/LICENSE-2.0
613
- *
614
- * Unless required by applicable law or agreed to in writing, software
615
- * distributed under the License is distributed on an "AS IS" BASIS,
616
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
617
- * See the License for the specific language governing permissions and
618
- * limitations under the License.
619
- */
620
-
621
589
  package context
622
590
 
623
591
  import zio.blocks.context._
@@ -687,22 +655,6 @@ sbt "schema-examples/runMain context.ContextRetrievalExample"
687
655
  **Context modification: adding values, updating existing ones, merging contexts, and pruning types**
688
656
 
689
657
  ```scala title="schema-examples/src/main/scala/context/ContextModificationExample.scala"
690
- /*
691
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
692
- *
693
- * Licensed under the Apache License, Version 2.0 (the "License");
694
- * you may not use this file except in compliance with the License.
695
- * You may obtain a copy of the License at
696
- *
697
- * http://www.apache.org/licenses/LICENSE-2.0
698
- *
699
- * Unless required by applicable law or agreed to in writing, software
700
- * distributed under the License is distributed on an "AS IS" BASIS,
701
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
702
- * See the License for the specific language governing permissions and
703
- * limitations under the License.
704
- */
705
-
706
658
  package context
707
659
 
708
660
  import zio.blocks.context._
@@ -775,3 +727,8 @@ object ContextModificationExample extends App {
775
727
  ```bash
776
728
  sbt "schema-examples/runMain context.ContextModificationExample"
777
729
  ```
730
+
731
+ ## See Also
732
+
733
+ - [Telemetry Reference](./telemetry/index.md) — Its `OtelContext` bridge snapshots the active `SpanContext` into a `Context[R & OtelContext]`
734
+ - [Telemetry Guide](../guides/telemetry-guide.md) — Architecture and patterns for the telemetry module, including where `Context` fits in