@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
@@ -1,6 +1,14 @@
1
1
  ---
2
2
  id: writer
3
3
  title: "Writer"
4
+ sidebar_label: "Writer"
5
+ description: "The push-based sink for elements: the write-and-close protocol, the primitive write family, and the sixteen deferred *Async mirrors."
6
+ keywords:
7
+ - "Push-Based Writing"
8
+ - "Deferred Effects"
9
+ - "Writer Cancellation"
10
+ - "Specialized Writes"
11
+ - "Writer"
4
12
  ---
5
13
 
6
14
  `Writer[-Elem]` is a **push-based sink for elements** that accepts values one at a time until closed or filled. It is the push-based counterpart to `Reader[+Elem]` (which pulls). Elements are written on demand by the producer, making it ideal for streaming, buffering, and integration with I/O subsystems. The fundamental operations are `write(elem): Boolean` — pushes an element and returns success or closure — and `close()` — signals the end of writing and releases resources.
@@ -31,7 +39,7 @@ Imagine you're building a data pipeline where a producer feeds items to a bounde
31
39
 
32
40
  With Java's `OutputStream`, you call `write()` and either it succeeds (void return) or throws an exception. This leaves ambiguity: Was the exception transient (try again later) or permanent (the stream is done)? If the buffer fills, the thread blocks—but you don't know how long, or even that it will block beforehand. There's no way to check capacity upfront, so you're forced to either over-allocate buffers (wasting memory) or catch exceptions and guess the right strategy.
33
41
 
34
- `Writer` makes the state explicit and non-throwing. You check readiness with `writeable()`, then push with `write()`, which returns a `Boolean` indicating success or closure. The protocol is clear and exception-free: when `write()` returns `false`, the sink is permanently closed and you should stop.
42
+ `Writer` makes the state explicit and non-throwing. You check readiness with `writeable()`, then push with `write()`, which returns a `Boolean` indicating success or closure. The protocol is explicit: when `write()` returns `false`, the sink is permanently closed and you should stop. It is not exception-free, though — a writer closed with `fail` may throw the stored error on a subsequent `write()`.
35
43
 
36
44
  ## Quick Showcase
37
45
 
@@ -55,7 +63,7 @@ val w = new Writer[Int] {
55
63
  override def fail(error: Throwable) = close()
56
64
  override def writeable() = !isClosed
57
65
  }
58
- // w: Writer[Int] = repl.MdocSession$MdocApp0$$anon$2@350ce732
66
+ // w: Writer[Int] = repl.MdocSession$MdocApp0$$anon$2@6cb38411
59
67
 
60
68
  // Push elements, checking writeable() before each write
61
69
  def pushAll(elements: List[Int]): Unit = {
@@ -83,7 +91,7 @@ The fundamental protocol is: call `write(element)` to push an element. It return
83
91
  import zio.blocks.streams.io.Writer
84
92
 
85
93
  val w = Writer.single[Int]
86
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@78b107f
94
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@2086fd7a
87
95
  println(s"First write: ${w.write(42)}") // true (accepted)
88
96
  // First write: true
89
97
  println(s"Second write: ${w.write(99)}") // false (writer auto-closed after one element)
@@ -92,14 +100,79 @@ println(s"Third write: ${w.write(77)}") // false (still closed)
92
100
  // Third write: false
93
101
  ```
94
102
 
103
+ ## Asynchronous Writes
104
+
105
+ Every effectful member of `Writer` has a deferred mirror whose name ends in `Async` and whose result is an `Async`. There are sixteen of them, and together they are the entire asynchronous surface of the type; the structural combinators `concat` and `contramap` are deliberately outside it.
106
+
107
+ A mirror does one thing. It wraps a single synchronous call in an effect that has not happened yet: constructing `writer.writeAsync(42)` performs no write at all, and driving the returned effect performs `write(42)` exactly once and yields its `Boolean`. All sixteen are `final` and delegate to one private helper, which builds them on the library's internal cancellable-defer primitive `Async.deferCancelable` (`Writer.scala:57`).
108
+
109
+ The mirrors group exactly as their synchronous twins do on this page:
110
+
111
+ | Group | Synchronous member | Deferred mirror | Result |
112
+ |--------------------|--------------------------------|-------------------------------------|-----------------------|
113
+ | Lifecycle | `close()` | `closeAsync()` | `Async[Unit]` |
114
+ | Lifecycle | `fail(error)` | `failAsync(error)` | `Async[Unit]` |
115
+ | Single element | `write(a)` | `writeAsync(a)` | `Async[Boolean]` |
116
+ | Bulk | `writeAll(chunk)` | `writeAllAsync(chunk)` | `Async[Chunk[Elem1]]` |
117
+ | Specialized | `writeInt(value)` | `writeIntAsync(value)` | `Async[Boolean]` |
118
+ | Specialized | `writeLong(value)` | `writeLongAsync(value)` | `Async[Boolean]` |
119
+ | Specialized | `writeFloat(value)` | `writeFloatAsync(value)` | `Async[Boolean]` |
120
+ | Specialized | `writeDouble(value)` | `writeDoubleAsync(value)` | `Async[Boolean]` |
121
+ | Byte and character | `writeByte(b)` | `writeByteAsync(b)` | `Async[Boolean]` |
122
+ | Byte and character | `writeBytes(buf, offset, len)` | `writeBytesAsync(buf, offset, len)` | `Async[Int]` |
123
+ | Byte and character | `writeChar(value)` | `writeCharAsync(value)` | `Async[Boolean]` |
124
+ | Byte and character | `writeShort(value)` | `writeShortAsync(value)` | `Async[Boolean]` |
125
+ | Byte and character | `writeBoolean(value)` | `writeBooleanAsync(value)` | `Async[Boolean]` |
126
+ | State checks | `isClosed` | `isClosedAsync` | `Async[Boolean]` |
127
+ | State checks | `writeable()` | `writeableAsync()` | `Async[Boolean]` |
128
+ | State checks | `jvmType` | `jvmTypeAsync` | `Async[JvmType]` |
129
+
130
+ Each mirror carries the same parameters and the same implicit evidence as its twin, so the specialized mirrors still ask for the subtype witness their twin asks for:
131
+
132
+ ```scala
133
+ abstract class Writer[-Elem] {
134
+ final def closeAsync(): Async[Unit]
135
+ final def writeAsync(a: Elem): Async[Boolean]
136
+ final def writeAllAsync[Elem1 <: Elem](chunk: Chunk[Elem1]): Async[Chunk[Elem1]]
137
+ final def writeIntAsync(value: Int)(implicit ev: Int <:< Elem): Async[Boolean]
138
+ final def writeBytesAsync(buf: Array[Byte], offset: Int, len: Int)(implicit ev: Byte <:< Elem): Async[Int]
139
+ }
140
+ ```
141
+
142
+ `jvmTypeAsync` is the mirror of `jvmType`, the writer's element representation (`JvmType.AnyRef` unless a subclass overrides it). It is the only mirror whose twin is not otherwise documented on this page.
143
+
144
+ ### What `*Async` Does and Does Not Do
145
+
146
+ These are **cancellation-aware deferral adapters, not asynchronous I/O**. The library's own scaladoc says so in as many words (`Writer.scala:51`), and it is worth repeating because sixteen methods named `*Async` invite the opposite conclusion.
147
+
148
+ What a mirror does:
149
+
150
+ - **It defers one synchronous operation.** The wrapped call is first evaluated when the effect is driven, never when it is constructed, and it runs at most once however many times the effect is composed.
151
+ - **It closes the writer on cancellation.** Every mirror installs `close()` as its cancellation hook. If cancellation wins before the operation finishes, the writer is closed and the operation's result is discarded rather than published — the run then delivers nothing at all, so a cancelled handle must never be given to `block`.
152
+
153
+ What a mirror does not do:
154
+
155
+ - **It does not move the write to another thread.** Driving `writeAsync` calls `write` on whichever thread is driving.
156
+ - **It does not make a blocking write nonblocking.** When `write` blocks — a bounded buffer with no space, a socket with a full send window — driving `writeAsync` blocks in the same place for the same duration. `writeBytesAsync` on a `Writer.fromOutputStream` is a `java.io.OutputStream.write` behind an `Async`, and that call blocks.
157
+
158
+ :::warning[These methods are not nonblocking I/O]
159
+ A `Writer` whose `write` blocks still blocks when you drive its `*Async` mirror. The mirrors buy you deferral and a cancellation hook; they do not buy you a nonblocking writer. If you need writes that genuinely suspend rather than block, that is a different writer, not a different method on this one.
160
+ :::
161
+
162
+ Cancellation is cooperative and interrupts no thread, so the hook cannot abort a call already inside a blocking `write`. It calls `close()`, and that helps exactly when closing the writer is what releases the blocked call — which is true of a writer whose blocking wait is woken by closure, and false of one that ignores its own closed flag while parked. See [Running#cancel](../../async.md#runningcancel) for what a cancelled run does and does not stop.
163
+
164
+ ### Why There Is No `concatAsync` or `contramapAsync`
165
+
166
+ The structural combinators `concat` and `contramap` have no mirrors, and that is deliberate rather than an omission. The synchronous `Writer` protocol requires `write` to return its `Boolean` immediately. A composition callback that produced an `Async` would have no honest way to report that result: `write` cannot return a pending value, and inventing one — blocking on it, or guessing `true` — would break the very protocol the page opens with. Modelling asynchronous composition needs a separate async-writer architecture, not another method here.
167
+
95
168
  ## Capacity and Buffering
96
169
 
97
170
  The default `writeable()` method returns `!isClosed`—it only tells you if the writer is closed, not whether the buffer has space. Bounded implementations can override `writeable()` to reflect remaining capacity, but this is not guaranteed by the interface. The important distinction:
98
171
 
99
172
  - **`writeable()` returns `false`**: the writer is closed (permanent state)
100
- - **`writeable()` returns `true` but `write()` would block**: the buffer is full but not closed; bounded implementations block the calling thread until space becomes available (they don't return `false`)
173
+ - **`writeable()` returns `true` but `write()` would block**: the buffer is full but not closed. What happens next is implementation-defined: a writer backed by a bounded buffer may block the calling thread until space becomes available, while the buffer-backed writers in this library instead auto-close and return `false`.
101
174
 
102
- Implementations like `ByteBufferWriter` auto-close when the buffer fills, turning the full state into closure. Others may block indefinitely waiting for space.
175
+ The writers behind `NioWriters.fromByteBuffer` and its typed variants auto-close when the buffer fills, turning the full state into closure. A writer you implement yourself may instead block indefinitely waiting for space.
103
176
 
104
177
  ## Error Handling
105
178
 
@@ -127,7 +200,7 @@ class ErrorStoringWriter extends Writer[Int] {
127
200
  }
128
201
 
129
202
  val w = new ErrorStoringWriter()
130
- // w: ErrorStoringWriter = repl.MdocSession$MdocApp9$ErrorStoringWriter@35e00b45
203
+ // w: ErrorStoringWriter = repl.MdocSession$MdocApp9$ErrorStoringWriter@2cf0305a
131
204
  w.fail(new Exception("Stream error"))
132
205
  try {
133
206
  w.write(42) // throws the stored error
@@ -159,7 +232,7 @@ Create a pre-closed writer that rejects all writes:
159
232
  import zio.blocks.streams.io.Writer
160
233
 
161
234
  val w = Writer.closed
162
- // w: Writer[Any] = zio.blocks.streams.io.Writer$$anon$1@603b3a16
235
+ // w: Writer[Any] = zio.blocks.streams.io.Writer$$anon$1@475df1d1
163
236
  println(w.write(42)) // false (closed)
164
237
  // false
165
238
  println(w.isClosed) // true
@@ -182,7 +255,7 @@ Create a writer that accepts exactly one element, then auto-closes:
182
255
  import zio.blocks.streams.io.Writer
183
256
 
184
257
  val w = Writer.single[Int]
185
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@20f8ee95
258
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@1959620c
186
259
  println(w.write(42)) // true
187
260
  // true
188
261
  println(w.write(99)) // false (already accepted one element)
@@ -218,10 +291,10 @@ val inner = new Writer[Int] {
218
291
  def write(a: Int) = { collected += a; true }
219
292
  def close() = ()
220
293
  }
221
- // inner: Writer[Int] = repl.MdocSession$MdocApp19$$anon$23@73ed153a
294
+ // inner: Writer[Int] = repl.MdocSession$MdocApp19$$anon$23@31c126b0
222
295
 
223
296
  val limited = Writer.limited(inner, 2)
224
- // limited: Writer[Int] = zio.blocks.streams.io.Writer$LimitedWriter@6e804688
297
+ // limited: Writer[Int] = zio.blocks.streams.io.Writer$LimitedWriter@2c2c5703
225
298
  println(limited.write(1)) // true
226
299
  // true
227
300
  println(limited.write(2)) // true (space available)
@@ -254,6 +327,8 @@ object Writer {
254
327
 
255
328
  The fundamental operations on `Writer` cover pushing elements one at a time, bulk operations, specialized writes for primitives, and state checks:
256
329
 
330
+ Each of these operations also has a deferred mirror, listed in [Asynchronous Writes](#asynchronous-writes) above.
331
+
257
332
  ### Writing Elements
258
333
 
259
334
  `Writer#write` — Pushes one element to the writer. Returns `true` on success, `false` if the writer is closed and cannot accept more elements. Throws if the writer was closed with an error via `Writer#fail`:
@@ -270,7 +345,7 @@ Write elements and observe the return value indicating success or closure:
270
345
  import zio.blocks.streams.io.Writer
271
346
 
272
347
  val w = Writer.single[Int]
273
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@5855f9a7
348
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@40fc0d5d
274
349
  val result1 = w.write(42)
275
350
  // result1: Boolean = true
276
351
  val result2 = w.write(99) // false, already closed
@@ -296,7 +371,7 @@ import zio.blocks.streams.io.Writer
296
371
  import zio.blocks.chunk.Chunk
297
372
 
298
373
  val w = Writer.single[Int]
299
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@4130dfc
374
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@3d1c8bf6
300
375
  val chunk = Chunk(1, 2, 3)
301
376
  // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
302
377
  val remaining = w.writeAll(chunk)
@@ -307,13 +382,13 @@ println(s"Remaining: $remaining") // Chunk(2, 3)
307
382
 
308
383
  ### Specialized Writes
309
384
 
310
- For primitive types, specialized write methods avoid boxing by using subtype witnesses.
385
+ For primitive types, specialized write methods take a subtype witness so that a writer backed by that primitive can override them and write the value without going through the generic `write`. The default bodies delegate to `write(value.asInstanceOf[Elem])`, so a writer that does not override them gains nothing.
311
386
 
312
387
  `writeInt` — Specialized `Int` write. Requires implicit evidence that `Int` is a subtype of `Elem`:
313
388
 
314
389
  ```scala
315
390
  abstract class Writer[-Elem] {
316
- def writeInt(value: Int)(using Int <:< Elem): Boolean
391
+ def writeInt(value: Int)(implicit ev: Int <:< Elem): Boolean
317
392
  }
318
393
  ```
319
394
 
@@ -321,7 +396,7 @@ abstract class Writer[-Elem] {
321
396
 
322
397
  ```scala
323
398
  abstract class Writer[-Elem] {
324
- def writeLong(value: Long)(using Long <:< Elem): Boolean
399
+ def writeLong(value: Long)(implicit ev: Long <:< Elem): Boolean
325
400
  }
326
401
  ```
327
402
 
@@ -329,7 +404,7 @@ abstract class Writer[-Elem] {
329
404
 
330
405
  ```scala
331
406
  abstract class Writer[-Elem] {
332
- def writeFloat(value: Float)(using Float <:< Elem): Boolean
407
+ def writeFloat(value: Float)(implicit ev: Float <:< Elem): Boolean
333
408
  }
334
409
  ```
335
410
 
@@ -337,7 +412,7 @@ abstract class Writer[-Elem] {
337
412
 
338
413
  ```scala
339
414
  abstract class Writer[-Elem] {
340
- def writeDouble(value: Double)(using Double <:< Elem): Boolean
415
+ def writeDouble(value: Double)(implicit ev: Double <:< Elem): Boolean
341
416
  }
342
417
  ```
343
418
 
@@ -347,7 +422,7 @@ abstract class Writer[-Elem] {
347
422
 
348
423
  ```scala
349
424
  abstract class Writer[-Elem] {
350
- def writeByte(b: Byte)(using Byte <:< Elem): Boolean
425
+ def writeByte(b: Byte)(implicit ev: Byte <:< Elem): Boolean
351
426
  }
352
427
  ```
353
428
 
@@ -355,7 +430,7 @@ abstract class Writer[-Elem] {
355
430
 
356
431
  ```scala
357
432
  abstract class Writer[-Elem] {
358
- def writeBytes(buf: Array[Byte], offset: Int, len: Int)(using Byte <:< Elem): Int
433
+ def writeBytes(buf: Array[Byte], offset: Int, len: Int)(implicit ev: Byte <:< Elem): Int
359
434
  }
360
435
  ```
361
436
 
@@ -363,7 +438,7 @@ abstract class Writer[-Elem] {
363
438
 
364
439
  ```scala
365
440
  abstract class Writer[-Elem] {
366
- def writeChar(value: Char)(using Char <:< Elem): Boolean
441
+ def writeChar(value: Char)(implicit ev: Char <:< Elem): Boolean
367
442
  }
368
443
  ```
369
444
 
@@ -371,7 +446,7 @@ abstract class Writer[-Elem] {
371
446
 
372
447
  ```scala
373
448
  abstract class Writer[-Elem] {
374
- def writeShort(value: Short)(using Short <:< Elem): Boolean
449
+ def writeShort(value: Short)(implicit ev: Short <:< Elem): Boolean
375
450
  }
376
451
  ```
377
452
 
@@ -379,7 +454,7 @@ abstract class Writer[-Elem] {
379
454
 
380
455
  ```scala
381
456
  abstract class Writer[-Elem] {
382
- def writeBoolean(value: Boolean)(using Boolean <:< Elem): Boolean
457
+ def writeBoolean(value: Boolean)(implicit ev: Boolean <:< Elem): Boolean
383
458
  }
384
459
  ```
385
460
 
@@ -393,7 +468,7 @@ abstract class Writer[-Elem] {
393
468
  }
394
469
  ```
395
470
 
396
- `writeable` — Returns `true` if the next `write()` would accept a value without blocking (space is available and the writer is not closed). Default returns `!isClosed`. Buffered writers override for accuracy. Note: the analogous method on `Reader` is named `readable()`, not `writable()`.
471
+ `writeable` — Returns `true` if the next `write()` would accept a value without blocking (space is available and the writer is not closed). Default returns `!isClosed`. A writer backed by a bounded buffer can override it for accuracy; none of the writers in this library does. Note the spelling: it is `writeable()`, not `writable()`; `Reader`'s counterpart is `readable()`.
397
472
 
398
473
  ```scala
399
474
  abstract class Writer[-Elem] {
@@ -407,7 +482,7 @@ Check writer capacity before writing:
407
482
  import zio.blocks.streams.io.Writer
408
483
 
409
484
  val w = Writer.single[Int]
410
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@35d64778
485
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@71a80be
411
486
  println(w.writeable()) // true
412
487
  // true
413
488
  w.write(42)
@@ -454,17 +529,17 @@ val w1 = new Writer[Int] {
454
529
  }
455
530
  def close() = ()
456
531
  }
457
- // w1: Writer[Int] = repl.MdocSession$MdocApp32$$anon$43@3bcbf5bd
532
+ // w1: Writer[Int] = repl.MdocSession$MdocApp32$$anon$43@2b669ef3
458
533
 
459
534
  val w2 = new Writer[Int] {
460
535
  def isClosed = false
461
536
  def write(a: Int) = { collected += a * 10; true }
462
537
  def close() = ()
463
538
  }
464
- // w2: Writer[Int] = repl.MdocSession$MdocApp32$$anon$45@3ed3f62
539
+ // w2: Writer[Int] = repl.MdocSession$MdocApp32$$anon$45@30a331ff
465
540
 
466
541
  val combined = w1 ++ w2
467
- // combined: Writer[Int] = zio.blocks.streams.io.Writer$ConcatWith@6f75d7cc
542
+ // combined: Writer[Int] = zio.blocks.streams.io.Writer$ConcatWith@21dd14f1
468
543
  combined.write(5)
469
544
  // res33: Boolean = true
470
545
  combined.write(20) // first writer rejects, switches to second
@@ -493,10 +568,10 @@ val stringWriter = new Writer[String] {
493
568
  def write(a: String) = { println(s"Writing: $a"); true }
494
569
  def close() = ()
495
570
  }
496
- // stringWriter: Writer[String] = repl.MdocSession$MdocApp36$$anon$51@23d463e2
571
+ // stringWriter: Writer[String] = repl.MdocSession$MdocApp36$$anon$51@31113f62
497
572
 
498
573
  val intWriter = stringWriter.contramap[Int](_.toString)
499
- // intWriter: Writer[Int] = zio.blocks.streams.io.Writer$Contramapped@254fb1b1
574
+ // intWriter: Writer[Int] = zio.blocks.streams.io.Writer$Contramapped@6a721b4f
500
575
  intWriter.write(42) // Prints: Writing: 42
501
576
  // Writing: 42
502
577
  // res37: Boolean = true
@@ -532,7 +607,7 @@ Close a writer with an error:
532
607
  import zio.blocks.streams.io.Writer
533
608
 
534
609
  val w = Writer.single[Int]
535
- // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@7e886598
610
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@11dd645a
536
611
  w.write(42)
537
612
  // res39: Boolean = true
538
613
  w.fail(new RuntimeException("Error"))
@@ -555,11 +630,11 @@ val numberWriter = new Writer[Number] {
555
630
  def write(a: Number) = { println(s"Number: $a"); true }
556
631
  def close() = ()
557
632
  }
558
- // numberWriter: Writer[Number] = repl.MdocSession$MdocApp42$$anon$59@290eac28
633
+ // numberWriter: Writer[Number] = repl.MdocSession$MdocApp42$$anon$59@4a564d3b
559
634
 
560
635
  // numberWriter is also a Writer[IntNum] due to contravariance
561
636
  val intNumWriter: Writer[IntNum] = numberWriter
562
- // intNumWriter: Writer[IntNum] = repl.MdocSession$MdocApp42$$anon$59@290eac28
637
+ // intNumWriter: Writer[IntNum] = repl.MdocSession$MdocApp42$$anon$59@4a564d3b
563
638
  intNumWriter.write(IntNum(42))
564
639
  // Number: IntNum(42)
565
640
  // res43: Boolean = true
@@ -577,7 +652,7 @@ For typical stream usage, you'll see Writer indirectly when writing to files, ne
577
652
 
578
653
  Understanding `Writer`'s design decisions helps you use it correctly and avoid common pitfalls:
579
654
 
580
- ### Push vs Pull
655
+ ### Push Vs Pull
581
656
 
582
657
  `Writer` is push-based (producer-driven), contrasting with `Reader` which is pull-based (consumer-driven):
583
658
 
@@ -586,7 +661,7 @@ Understanding `Writer`'s design decisions helps you use it correctly and avoid c
586
661
  | **Direction** | Source → Consumer (pull) | Producer → Sink (push) |
587
662
  | **Variance** | Covariant (`+Elem`) | Contravariant (`−Elem`) |
588
663
  | **Blocking** | `read()` may block | `write()` may block |
589
- | **Signal end** | Returns sentinel or `null` | `close()` or `fail()` |
664
+ | **Signal end** | Caller-supplied sentinel, or a negative count from a bulk read | `close()` or `fail()` |
590
665
  | **Dual** | Sink drains Reader | Producer feeds Writer |
591
666
 
592
667
  ### Thread Safety
@@ -617,22 +692,6 @@ cd zio-blocks
617
692
  This example demonstrates the most common writer factories: `Writer.single`, `Writer.limited`, `Writer.closed`, and custom writers via subclassing:
618
693
 
619
694
  ```scala title="streams-examples/src/main/scala/writer/WriterBasicConstructionExample.scala"
620
- /*
621
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
622
- *
623
- * Licensed under the Apache License, Version 2.0 (the "License");
624
- * you may not use this file except in compliance with the License.
625
- * You may obtain a copy of the License at
626
- *
627
- * http://www.apache.org/licenses/LICENSE-2.0
628
- *
629
- * Unless required by applicable law or agreed to in writing, software
630
- * distributed under the License is distributed on an "AS IS" BASIS,
631
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
632
- * See the License for the specific language governing permissions and
633
- * limitations under the License.
634
- */
635
-
636
695
  package writer
637
696
 
638
697
  import zio.blocks.streams.io.Writer
@@ -714,22 +773,6 @@ sbt "streams-examples/runMain writer.WriterBasicConstructionExample"
714
773
  This example shows writer composition with `Writer#++` (concat), transformation with `Writer#contramap`, and bulk writes with `Writer#writeAll`:
715
774
 
716
775
  ```scala title="streams-examples/src/main/scala/writer/WriterCompositionExample.scala"
717
- /*
718
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
719
- *
720
- * Licensed under the Apache License, Version 2.0 (the "License");
721
- * you may not use this file except in compliance with the License.
722
- * You may obtain a copy of the License at
723
- *
724
- * http://www.apache.org/licenses/LICENSE-2.0
725
- *
726
- * Unless required by applicable law or agreed to in writing, software
727
- * distributed under the License is distributed on an "AS IS" BASIS,
728
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
729
- * See the License for the specific language governing permissions and
730
- * limitations under the License.
731
- */
732
-
733
776
  package writer
734
777
 
735
778
  import zio.blocks.streams.io.Writer
@@ -851,22 +894,6 @@ sbt "streams-examples/runMain writer.WriterCompositionExample"
851
894
  This example demonstrates I/O integration with `Writer.fromOutputStream` and `Writer.fromWriter` for streaming to files or character streams:
852
895
 
853
896
  ```scala title="streams-examples/src/main/scala/writer/WriterIOAdapterExample.scala"
854
- /*
855
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
856
- *
857
- * Licensed under the Apache License, Version 2.0 (the "License");
858
- * you may not use this file except in compliance with the License.
859
- * You may obtain a copy of the License at
860
- *
861
- * http://www.apache.org/licenses/LICENSE-2.0
862
- *
863
- * Unless required by applicable law or agreed to in writing, software
864
- * distributed under the License is distributed on an "AS IS" BASIS,
865
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
866
- * See the License for the specific language governing permissions and
867
- * limitations under the License.
868
- */
869
-
870
897
  package writer
871
898
 
872
899
  import zio.blocks.streams.io.Writer
@@ -965,22 +992,6 @@ sbt "streams-examples/runMain writer.WriterIOAdapterExample"
965
992
  This example shows how to implement a bounded Writer that wraps a fixed-capacity container and auto-closes when full. It demonstrates the protocol: `write()` returns `false` only on closure (not buffer fullness), and `writeable()` reflects closure state:
966
993
 
967
994
  ```scala title="streams-examples/src/main/scala/writer/WriterBoundedImplementationExample.scala"
968
- /*
969
- * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
970
- *
971
- * Licensed under the Apache License, Version 2.0 (the "License");
972
- * you may not use this file except in compliance with the License.
973
- * You may obtain a copy of the License at
974
- *
975
- * http://www.apache.org/licenses/LICENSE-2.0
976
- *
977
- * Unless required by applicable law or agreed to in writing, software
978
- * distributed under the License is distributed on an "AS IS" BASIS,
979
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
980
- * See the License for the specific language governing permissions and
981
- * limitations under the License.
982
- */
983
-
984
995
  package writer
985
996
 
986
997
  import zio.blocks.streams.io.Writer
@@ -1043,3 +1054,148 @@ Run this example with:
1043
1054
  ```bash
1044
1055
  sbt "streams-examples/runMain writer.WriterBoundedImplementationExample"
1045
1056
  ```
1057
+
1058
+ ### Deferred and Cancellable Writes
1059
+
1060
+ This example makes the `*Async` caveat concrete. It shows that constructing a mirror writes nothing while driving it writes once, composes three mirrors into one effect, and then cancels a driven `writeAsync` whose write is parked — observing that cancellation closed the writer, that no element was recorded, and that the run delivers no value at all:
1061
+
1062
+ ```scala title="streams-examples/src/main/scala/writer/WriterAsyncExample.scala"
1063
+ package writer
1064
+
1065
+ import zio.blocks.async.*
1066
+ import zio.blocks.chunk.Chunk
1067
+ import zio.blocks.streams.io.Writer
1068
+
1069
+ import java.util.concurrent.{ConcurrentLinkedQueue, CountDownLatch}
1070
+
1071
+ /**
1072
+ * The `*Async` mirrors on `Writer`, and the two properties that define them:
1073
+ * each mirror defers exactly one synchronous writer operation until the
1074
+ * returned effect is driven, and cancellation closes the writer and suppresses
1075
+ * the stale result.
1076
+ *
1077
+ * Neither property makes a write nonblocking, and neither moves it to another
1078
+ * thread. The last section only observes cancellation at all because the
1079
+ * writer's own `close()` is what releases the parked write.
1080
+ *
1081
+ * JVM only, because it ends in `.block` to turn an `Async` into a value for
1082
+ * `main`.
1083
+ */
1084
+ object WriterAsyncExample {
1085
+
1086
+ /** Records what actually reached the writer, so deferral is observable. */
1087
+ final class RecordingWriter extends Writer[Int] {
1088
+ private val recorded = scala.collection.mutable.ArrayBuffer.empty[Int]
1089
+ private var closed = false
1090
+ def isClosed: Boolean = closed
1091
+ def write(a: Int): Boolean = if (closed) false else { recorded += a; true }
1092
+ def close(): Unit = closed = true
1093
+ def snapshot: List[Int] = recorded.toList
1094
+ }
1095
+
1096
+ /**
1097
+ * A writer whose `write` parks until someone closes it. `close()` is the
1098
+ * cancellation hook every `*Async` mirror installs, so cancelling a driven
1099
+ * `writeAsync` is what wakes this writer up again.
1100
+ */
1101
+ final class GatedWriter extends Writer[Int] {
1102
+ private val gate = new CountDownLatch(1)
1103
+ private val recorded = new ConcurrentLinkedQueue[Int]
1104
+ @volatile private var closed = false
1105
+
1106
+ /** Counts down once the deferred thunk has entered `write`. */
1107
+ val entered = new CountDownLatch(1)
1108
+
1109
+ /** Counts down once `close()` has run. */
1110
+ val wasClosed = new CountDownLatch(1)
1111
+
1112
+ /** Counts down once the parked `write` has returned. */
1113
+ val finished = new CountDownLatch(1)
1114
+
1115
+ def isClosed: Boolean = closed
1116
+
1117
+ def write(a: Int): Boolean = {
1118
+ entered.countDown()
1119
+ gate.await()
1120
+ val accepted =
1121
+ if (closed) false
1122
+ else { recorded.add(a); true }
1123
+ finished.countDown()
1124
+ accepted
1125
+ }
1126
+
1127
+ def close(): Unit = {
1128
+ closed = true
1129
+ gate.countDown()
1130
+ wasClosed.countDown()
1131
+ }
1132
+
1133
+ def recordedCount: Int = recorded.size
1134
+ }
1135
+
1136
+ def main(args: Array[String]): Unit = {
1137
+ deferral()
1138
+ sequence()
1139
+ cancellation()
1140
+ }
1141
+
1142
+ /** Constructing a mirror writes nothing; driving it writes exactly once. */
1143
+ private def deferral(): Unit = {
1144
+ val writer = new RecordingWriter
1145
+ val pending = writer.writeAsync(1)
1146
+
1147
+ println(s"deferral: after construction recorded=${writer.snapshot}")
1148
+ println(s"deferral: after driving once accepted=${pending.block}, recorded=${writer.snapshot}")
1149
+ }
1150
+
1151
+ /** The mirrors compose like any other `Async`, one operation per step. */
1152
+ private def sequence(): Unit = {
1153
+ val writer = new RecordingWriter
1154
+
1155
+ val program: Async[Chunk[Int]] =
1156
+ writer
1157
+ .writeAsync(10)
1158
+ .flatMap(_ => writer.writeAllAsync(Chunk(20, 30, 40)))
1159
+ .flatMap(undelivered => writer.closeAsync().map(_ => undelivered))
1160
+
1161
+ val undelivered = program.block
1162
+ println(s"sequence: recorded=${writer.snapshot}, undelivered=$undelivered, closed=${writer.isClosed}")
1163
+ }
1164
+
1165
+ /**
1166
+ * Cancellation closes the writer and discards the result it was about to
1167
+ * produce.
1168
+ */
1169
+ private def cancellation(): Unit = {
1170
+ val writer = new GatedWriter
1171
+ val running = writer.writeAsync(99).start
1172
+
1173
+ // Wait until the deferred thunk is parked inside `write`, so cancellation
1174
+ // races a genuinely in-flight operation rather than an unstarted one.
1175
+ writer.entered.await()
1176
+ running.cancel()
1177
+
1178
+ // Cancellation ran `close()`, which released the parked `write`.
1179
+ writer.wasClosed.await()
1180
+ writer.finished.await()
1181
+
1182
+ // The `false` that `write` then returned lost the race to publish, so this
1183
+ // run never delivers a value. Never call `.block` on a cancelled handle.
1184
+ println(s"cancellation: closed=${writer.isClosed}, recorded=${writer.recordedCount}")
1185
+ }
1186
+ }
1187
+ ```
1188
+
1189
+ Run this example with:
1190
+
1191
+ ```bash
1192
+ sbt "streams-examples/runMain writer.WriterAsyncExample"
1193
+ ```
1194
+
1195
+ ## See Also
1196
+
1197
+ - [Asynchronous Stream Execution](../execution-and-compatibility/async-execution.md#cancellation) — how cancellation reaches a stream's resources, and the asynchronous stream API the deferred mirrors sit beside
1198
+ - [Async Reference](../../async.md#runningcancel) — what `Running#cancel` stops, why a cancelled run never delivers, and why the cancel hook cannot interrupt a blocked thread
1199
+ - [Reader](./reader.md) — the pull-based dual of this type, and the reader kinds a sink drains
1200
+ - [Sink](../core/sink.md) — the consumer side of a stream, which drains a `Reader` rather than feeding a `Writer`
1201
+ - [Zero-Boxing Streams](../execution-and-compatibility/zero-boxing.md) — why the specialized write family exists and how a primitive lane is chosen