@zio.dev/zio-blocks 0.0.33 → 0.0.51

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 (150) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +16 -17
  2. package/guides/getting-started-with-mux.md +1507 -0
  3. package/guides/query-dsl-extending.md +161 -102
  4. package/guides/query-dsl-fluent-builder.md +217 -157
  5. package/guides/query-dsl-reified-optics.md +12 -10
  6. package/guides/query-dsl-sql.md +246 -165
  7. package/guides/telemetry-guide.md +1069 -0
  8. package/guides/zio-schema-migration.md +29 -22
  9. package/index.md +292 -50
  10. package/package.json +1 -1
  11. package/plans/config-follow-up-prs.md +188 -0
  12. package/plans/config-pr-assessment-roadmap.md +310 -0
  13. package/reference/MuxDataFlow.jsx +250 -0
  14. package/reference/async.md +651 -0
  15. package/reference/chunk.md +3533 -308
  16. package/reference/codegen/case-class.md +436 -0
  17. package/reference/codegen/emitter-config.md +383 -0
  18. package/reference/codegen/examples.md +664 -0
  19. package/reference/codegen/field.md +316 -0
  20. package/reference/codegen/index.md +317 -0
  21. package/reference/codegen/scala-emitter.md +392 -0
  22. package/reference/codegen/scala-file.md +276 -0
  23. package/reference/codegen/sealed-trait.md +408 -0
  24. package/reference/codegen/type-definition.md +340 -0
  25. package/reference/codegen/type-ref.md +201 -0
  26. package/reference/combinators.md +347 -117
  27. package/reference/config.md +158 -0
  28. package/reference/context.md +4 -4
  29. package/reference/datastar.md +346 -0
  30. package/reference/docs.md +1461 -345
  31. package/reference/endpoint/auth-type.md +146 -0
  32. package/reference/endpoint/endpoint.md +297 -0
  33. package/reference/endpoint/http-codec.md +249 -0
  34. package/reference/endpoint/index.md +825 -0
  35. package/reference/endpoint/path-codec.md +237 -0
  36. package/reference/endpoint/route-pattern.md +196 -0
  37. package/reference/endpoint/route-tree.md +111 -0
  38. package/reference/endpoint/segment-codec.md +212 -0
  39. package/reference/html.md +1120 -0
  40. package/reference/htmx/attribute-values.md +359 -0
  41. package/reference/htmx/hx-encoding.md +111 -0
  42. package/reference/htmx/hx-params.md +204 -0
  43. package/reference/htmx/hx-swap.md +276 -0
  44. package/reference/htmx/hx-sync.md +251 -0
  45. package/reference/htmx/hx-target.md +314 -0
  46. package/reference/htmx/hx-trigger.md +457 -0
  47. package/reference/htmx/hx-url-update.md +239 -0
  48. package/reference/htmx/index.md +855 -0
  49. package/reference/http-model/index.md +47 -0
  50. package/reference/http-model/model.md +1481 -0
  51. package/reference/http-model/schema.md +747 -0
  52. package/reference/maybe.md +826 -0
  53. package/reference/media-type.md +2 -2
  54. package/reference/mux.mdx +823 -0
  55. package/reference/openapi.md +1351 -0
  56. package/reference/resource-management/defer-handle.md +1 -1
  57. package/reference/resource-management/resource.md +31 -2
  58. package/reference/resource-management/scope.md +28 -12
  59. package/reference/resource-management/wire.md +3 -7
  60. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  61. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  62. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  63. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  64. package/reference/ringbuffer/advanced.mdx +109 -0
  65. package/reference/ringbuffer/index.mdx +145 -0
  66. package/reference/ringbuffer/mpmc.mdx +151 -0
  67. package/reference/ringbuffer/mpsc.mdx +132 -0
  68. package/reference/ringbuffer/spmc.mdx +108 -0
  69. package/reference/ringbuffer/spsc.mdx +344 -0
  70. package/reference/{allows.md → schema/allows.md} +4 -4
  71. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  72. package/reference/{binding.md → schema/binding.md} +2 -3
  73. package/reference/schema/built-in-codecs/avro.md +451 -0
  74. package/reference/schema/built-in-codecs/bson.md +480 -0
  75. package/reference/schema/built-in-codecs/csv.md +564 -0
  76. package/reference/schema/built-in-codecs/index.md +77 -0
  77. package/reference/schema/built-in-codecs/json/index.md +295 -0
  78. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  79. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  80. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  81. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  82. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  83. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  84. package/reference/schema/built-in-codecs/thrift.md +433 -0
  85. package/reference/schema/built-in-codecs/toon.md +1078 -0
  86. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  87. package/reference/schema/built-in-codecs/yaml.md +552 -0
  88. package/reference/{codec.md → schema/codec.md} +10 -10
  89. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +151 -5
  90. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  91. package/reference/schema/format.md +92 -0
  92. package/reference/schema/index.md +50 -0
  93. package/reference/schema/migration.md +297 -0
  94. package/reference/{modifier.md → schema/modifier.md} +58 -7
  95. package/reference/{optics.md → schema/optics.md} +2 -2
  96. package/reference/{patch.md → schema/patch.md} +1 -1
  97. package/{path-interpolator.md → reference/schema/path-interpolator.md} +165 -72
  98. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  99. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  100. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  101. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  102. package/reference/{schema.md → schema/schema.md} +12 -0
  103. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  104. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  105. package/reference/smithy.md +533 -0
  106. package/reference/sql/db-codec-deriver.md +71 -0
  107. package/reference/sql/db-codec.md +687 -0
  108. package/reference/sql/db-con.md +271 -0
  109. package/reference/sql/db-connection.md +153 -0
  110. package/reference/sql/db-param-writer.md +77 -0
  111. package/reference/sql/db-param.md +66 -0
  112. package/reference/sql/db-result-reader.md +146 -0
  113. package/reference/sql/db-tx.md +82 -0
  114. package/reference/sql/db-value.md +41 -0
  115. package/reference/sql/ddl.md +85 -0
  116. package/reference/sql/frag.md +254 -0
  117. package/reference/sql/index.md +341 -0
  118. package/reference/sql/repo.md +600 -0
  119. package/reference/sql/sql-dialect.md +73 -0
  120. package/reference/sql/sql-logger.md +62 -0
  121. package/reference/sql/sql-name-mapper.md +70 -0
  122. package/reference/sql/table-metadata.md +134 -0
  123. package/reference/sql/table.md +448 -0
  124. package/reference/sql/transactor-zio.md +399 -0
  125. package/reference/sql/transactor.md +353 -0
  126. package/reference/sql-zio.md +112 -0
  127. package/reference/streams/concurrent-operators.md +106 -0
  128. package/reference/streams/index.md +653 -0
  129. package/reference/streams/pipeline.md +718 -0
  130. package/reference/streams/reader.md +1284 -0
  131. package/reference/streams/scala-2-compatibility.md +55 -0
  132. package/reference/streams/sink.md +1426 -0
  133. package/reference/streams/stream.md +2526 -0
  134. package/reference/streams/writer.md +1045 -0
  135. package/reference/streams/zero-boxing.md +275 -0
  136. package/reference/telemetry.md +693 -0
  137. package/reference/typeid.md +5 -19
  138. package/sidebars.js +238 -43
  139. package/reference/formats.md +0 -694
  140. package/reference/http-model.md +0 -1716
  141. package/reference/streams.md +0 -989
  142. package/ringbuffer.md +0 -249
  143. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  144. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  145. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  146. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  147. /package/reference/{registers.md → schema/registers.md} +0 -0
  148. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  149. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  150. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,1045 @@
1
+ ---
2
+ id: writer
3
+ title: "Writer"
4
+ ---
5
+
6
+ `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.
7
+
8
+ `Writer[-Elem]` has these key properties:
9
+
10
+ - **Lazy and Push-Based** — nothing happens until the producer calls `write()`
11
+ - **Non-Thread-Safe** — designed for single-threaded production; concurrent access requires external synchronization
12
+ - **Explicit Closure Signal** — returns `false` when closed (clean closure) or throws when error-closed
13
+
14
+ Here is the structural shape of the `Writer` type:
15
+
16
+ ```scala
17
+ abstract class Writer[-Elem] {
18
+ def write(a: Elem): Boolean
19
+ def close(): Unit
20
+ def isClosed: Boolean
21
+
22
+ // concrete defaults for fail() and writeable()
23
+ def fail(error: Throwable): Unit = close()
24
+ def writeable(): Boolean = !isClosed
25
+ }
26
+ ```
27
+
28
+ ## Motivation
29
+
30
+ Imagine you're building a data pipeline where a producer feeds items to a bounded sink. The producer doesn't control the sink's internal state—how much capacity remains, whether it's busy, or if it's permanently closed. You need to know before each write: Is the sink ready? Did the write succeed? Is the sink closed?
31
+
32
+ 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
+
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.
35
+
36
+ ## Quick Showcase
37
+
38
+ Here's how to create and push elements to a `Writer`:
39
+
40
+ ```scala
41
+ import zio.blocks.streams.io.Writer
42
+ import scala.collection.mutable.Buffer
43
+
44
+ val collected = Buffer[Int]()
45
+ // collected: Buffer[Int] = ArrayBuffer(10, 20, 30, 40, 50)
46
+ val w = new Writer[Int] {
47
+ private var closed = false
48
+
49
+ def isClosed = closed
50
+ def write(a: Int) = {
51
+ if (!closed) { collected += a; true }
52
+ else false
53
+ }
54
+ def close() = { closed = true }
55
+ override def fail(error: Throwable) = close()
56
+ override def writeable() = !isClosed
57
+ }
58
+ // w: Writer[Int] = repl.MdocSession$MdocApp0$$anon$2@350ce732
59
+
60
+ // Push elements, checking writeable() before each write
61
+ def pushAll(elements: List[Int]): Unit = {
62
+ elements match {
63
+ case Nil => ()
64
+ case head :: tail =>
65
+ if (w.writeable() && w.write(head)) pushAll(tail)
66
+ }
67
+ }
68
+
69
+ pushAll(List(10, 20, 30, 40, 50))
70
+ w.close()
71
+
72
+ println(s"Collected: $collected")
73
+ // Collected: ArrayBuffer(10, 20, 30, 40, 50)
74
+ println(s"Writable after close: ${w.writeable()}")
75
+ // Writable after close: false
76
+ ```
77
+
78
+ ## Writing and Closure
79
+
80
+ The fundamental protocol is: call `write(element)` to push an element. It returns `true` on success, `false` only when the writer is **closed** (not when the buffer is full). Once `write()` returns `false`, the writer is permanently closed—all further writes return `false`. There is no recovery.
81
+
82
+ ```scala
83
+ import zio.blocks.streams.io.Writer
84
+
85
+ val w = Writer.single[Int]
86
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@78b107f
87
+ println(s"First write: ${w.write(42)}") // true (accepted)
88
+ // First write: true
89
+ println(s"Second write: ${w.write(99)}") // false (writer auto-closed after one element)
90
+ // Second write: false
91
+ println(s"Third write: ${w.write(77)}") // false (still closed)
92
+ // Third write: false
93
+ ```
94
+
95
+ ## Capacity and Buffering
96
+
97
+ 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
+
99
+ - **`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`)
101
+
102
+ Implementations like `ByteBufferWriter` auto-close when the buffer fills, turning the full state into closure. Others may block indefinitely waiting for space.
103
+
104
+ ## Error Handling
105
+
106
+ When the writer encounters an error, signal it with `fail(error)`. By default, `fail()` closes the writer; all subsequent `write()` calls return `false`.
107
+
108
+ If you override `fail()` to store the error internally, `write()` will throw it on the next call:
109
+
110
+ ```scala
111
+ import zio.blocks.streams.io.Writer
112
+
113
+ class ErrorStoringWriter extends Writer[Int] {
114
+ private var closed = false
115
+ private var storedError: Option[Throwable] = None
116
+
117
+ def isClosed = closed
118
+ def write(a: Int): Boolean = {
119
+ if (storedError.isDefined) throw storedError.get
120
+ if (closed) false else true
121
+ }
122
+ def close() = { closed = true }
123
+ override def fail(error: Throwable) = {
124
+ storedError = Some(error)
125
+ closed = true
126
+ }
127
+ }
128
+
129
+ val w = new ErrorStoringWriter()
130
+ // w: ErrorStoringWriter = repl.MdocSession$MdocApp9$ErrorStoringWriter@35e00b45
131
+ w.fail(new Exception("Stream error"))
132
+ try {
133
+ w.write(42) // throws the stored error
134
+ } catch {
135
+ case e: Exception => println(s"Caught: ${e.getMessage}")
136
+ }
137
+ // Caught: Stream error
138
+ ```
139
+
140
+ This gives you optional error propagation: use the default `fail()` for silent closure, or override it to propagate errors as exceptions.
141
+
142
+ ## Construction
143
+
144
+ Writers are created using factory methods on the companion object, from adapters wrapping Java I/O, or by direct subclassing for custom implementations:
145
+
146
+ ### Creating Predefined Writers
147
+
148
+ `Writer.closed` — A pre-closed writer that rejects all writes. Useful as a base case for empty streams:
149
+
150
+ ```scala
151
+ object Writer {
152
+ def closed: Writer[Any]
153
+ }
154
+ ```
155
+
156
+ Create a pre-closed writer that rejects all writes:
157
+
158
+ ```scala
159
+ import zio.blocks.streams.io.Writer
160
+
161
+ val w = Writer.closed
162
+ // w: Writer[Any] = zio.blocks.streams.io.Writer$$anon$1@603b3a16
163
+ println(w.write(42)) // false (closed)
164
+ // false
165
+ println(w.isClosed) // true
166
+ // true
167
+ ```
168
+
169
+ ### Single Element
170
+
171
+ `Writer.single` — Creates a writer that accepts exactly one element, then auto-closes. The dual of `Reader.single`:
172
+
173
+ ```scala
174
+ object Writer {
175
+ def single[Elem]: Writer[Elem]
176
+ }
177
+ ```
178
+
179
+ Create a writer that accepts exactly one element, then auto-closes:
180
+
181
+ ```scala
182
+ import zio.blocks.streams.io.Writer
183
+
184
+ val w = Writer.single[Int]
185
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@20f8ee95
186
+ println(w.write(42)) // true
187
+ // true
188
+ println(w.write(99)) // false (already accepted one element)
189
+ // false
190
+ println(w.isClosed) // true
191
+ // true
192
+ ```
193
+
194
+ ### Limited Capacity
195
+
196
+ `Writer.limited` — Creates a writer that accepts at most `n` elements from `inner`, then becomes closed. The dual of `Stream.take`. If `inner` closes before `n` elements are accepted, the limited writer also closes immediately without consuming the remaining capacity.
197
+
198
+ :::note
199
+ The inner writer is not automatically closed—only the limited wrapper's `isClosed` returns `true` when the limit is reached. The inner writer stays open until someone explicitly calls `close()`.
200
+ :::
201
+
202
+ ```scala
203
+ object Writer {
204
+ def limited[Elem](inner: Writer[Elem], n: Long): Writer[Elem]
205
+ }
206
+ ```
207
+
208
+ Limit a writer to accept at most n elements:
209
+
210
+ ```scala
211
+ import zio.blocks.streams.io.Writer
212
+ import scala.collection.mutable.Buffer
213
+
214
+ val collected = Buffer[Int]()
215
+ // collected: Buffer[Int] = ArrayBuffer(1, 2)
216
+ val inner = new Writer[Int] {
217
+ def isClosed = false
218
+ def write(a: Int) = { collected += a; true }
219
+ def close() = ()
220
+ }
221
+ // inner: Writer[Int] = repl.MdocSession$MdocApp19$$anon$23@73ed153a
222
+
223
+ val limited = Writer.limited(inner, 2)
224
+ // limited: Writer[Int] = zio.blocks.streams.io.Writer$LimitedWriter@6e804688
225
+ println(limited.write(1)) // true
226
+ // true
227
+ println(limited.write(2)) // true (space available)
228
+ // true
229
+ println(limited.write(3)) // false (limit of 2 reached)
230
+ // false
231
+ println(s"Collected: $collected") // Collected: Buffer(1, 2)
232
+ // Collected: ArrayBuffer(1, 2)
233
+ ```
234
+
235
+ ### I/O Adapters
236
+
237
+ `Writer.fromOutputStream` — Wraps a `java.io.OutputStream` as a `Writer[Byte]`. Calling `close()` flushes and closes the underlying stream:
238
+
239
+ ```scala
240
+ object Writer {
241
+ def fromOutputStream(os: OutputStream): Writer[Byte]
242
+ }
243
+ ```
244
+
245
+ `Writer.fromWriter` — Wraps a `java.io.Writer` as a `Writer[Char]`. Calling `close()` flushes and closes the underlying writer:
246
+
247
+ ```scala
248
+ object Writer {
249
+ def fromWriter(w: java.io.Writer): Writer[Char]
250
+ }
251
+ ```
252
+
253
+ ## Core Operations
254
+
255
+ The fundamental operations on `Writer` cover pushing elements one at a time, bulk operations, specialized writes for primitives, and state checks:
256
+
257
+ ### Writing Elements
258
+
259
+ `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`:
260
+
261
+ ```scala
262
+ abstract class Writer[-Elem] {
263
+ def write(a: Elem): Boolean
264
+ }
265
+ ```
266
+
267
+ Write elements and observe the return value indicating success or closure:
268
+
269
+ ```scala
270
+ import zio.blocks.streams.io.Writer
271
+
272
+ val w = Writer.single[Int]
273
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@5855f9a7
274
+ val result1 = w.write(42)
275
+ // result1: Boolean = true
276
+ val result2 = w.write(99) // false, already closed
277
+ // result2: Boolean = false
278
+ println(s"First: $result1, Second: $result2")
279
+ // First: true, Second: false
280
+ ```
281
+
282
+ ### Bulk Writing
283
+
284
+ `Writer#writeAll` — Writes every element in a chunk. Returns the suffix not delivered. If the writer is already closed, returns the entire chunk. Exceptions from individual writes propagate to the caller:
285
+
286
+ ```scala
287
+ abstract class Writer[-Elem] {
288
+ def writeAll[Elem1 <: Elem](chunk: Chunk[Elem1]): Chunk[Elem1]
289
+ }
290
+ ```
291
+
292
+ Write a chunk and observe how many elements were delivered:
293
+
294
+ ```scala
295
+ import zio.blocks.streams.io.Writer
296
+ import zio.blocks.chunk.Chunk
297
+
298
+ val w = Writer.single[Int]
299
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@4130dfc
300
+ val chunk = Chunk(1, 2, 3)
301
+ // chunk: Chunk[Int] = IndexedSeq(1, 2, 3)
302
+ val remaining = w.writeAll(chunk)
303
+ // remaining: Chunk[Int] = IndexedSeq(2, 3)
304
+ println(s"Remaining: $remaining") // Chunk(2, 3)
305
+ // Remaining: Chunk(2,3)
306
+ ```
307
+
308
+ ### Specialized Writes
309
+
310
+ For primitive types, specialized write methods avoid boxing by using subtype witnesses.
311
+
312
+ `writeInt` — Specialized `Int` write. Requires implicit evidence that `Int` is a subtype of `Elem`:
313
+
314
+ ```scala
315
+ abstract class Writer[-Elem] {
316
+ def writeInt(value: Int)(using Int <:< Elem): Boolean
317
+ }
318
+ ```
319
+
320
+ `writeLong` — Specialized `Long` write:
321
+
322
+ ```scala
323
+ abstract class Writer[-Elem] {
324
+ def writeLong(value: Long)(using Long <:< Elem): Boolean
325
+ }
326
+ ```
327
+
328
+ `writeFloat` — Specialized `Float` write:
329
+
330
+ ```scala
331
+ abstract class Writer[-Elem] {
332
+ def writeFloat(value: Float)(using Float <:< Elem): Boolean
333
+ }
334
+ ```
335
+
336
+ `writeDouble` — Specialized `Double` write:
337
+
338
+ ```scala
339
+ abstract class Writer[-Elem] {
340
+ def writeDouble(value: Double)(using Double <:< Elem): Boolean
341
+ }
342
+ ```
343
+
344
+ ### Byte and Character Writes
345
+
346
+ `writeByte` — Specialized byte write. Avoids boxing when `Elem = Byte`. Requires evidence that `Byte` is a subtype of `Elem`:
347
+
348
+ ```scala
349
+ abstract class Writer[-Elem] {
350
+ def writeByte(b: Byte)(using Byte <:< Elem): Boolean
351
+ }
352
+ ```
353
+
354
+ `writeBytes` — Blocking bulk byte write. Calls `writeByte` for each byte in `buf[offset, offset+len)`, stopping early if the channel closes. Returns the number of bytes successfully written:
355
+
356
+ ```scala
357
+ abstract class Writer[-Elem] {
358
+ def writeBytes(buf: Array[Byte], offset: Int, len: Int)(using Byte <:< Elem): Int
359
+ }
360
+ ```
361
+
362
+ `writeChar` — Specialized `Char` write. Requires evidence that `Char` is a subtype of `Elem`:
363
+
364
+ ```scala
365
+ abstract class Writer[-Elem] {
366
+ def writeChar(value: Char)(using Char <:< Elem): Boolean
367
+ }
368
+ ```
369
+
370
+ `writeShort` — Specialized `Short` write. Requires evidence that `Short` is a subtype of `Elem`:
371
+
372
+ ```scala
373
+ abstract class Writer[-Elem] {
374
+ def writeShort(value: Short)(using Short <:< Elem): Boolean
375
+ }
376
+ ```
377
+
378
+ `writeBoolean` — Specialized `Boolean` write. Requires evidence that `Boolean` is a subtype of `Elem`:
379
+
380
+ ```scala
381
+ abstract class Writer[-Elem] {
382
+ def writeBoolean(value: Boolean)(using Boolean <:< Elem): Boolean
383
+ }
384
+ ```
385
+
386
+ ### State Checks
387
+
388
+ `Writer#isClosed` — Returns `true` if the writer is closed. Monotone: once `true`, never returns `false`:
389
+
390
+ ```scala
391
+ abstract class Writer[-Elem] {
392
+ def isClosed: Boolean
393
+ }
394
+ ```
395
+
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()`.
397
+
398
+ ```scala
399
+ abstract class Writer[-Elem] {
400
+ def writeable(): Boolean
401
+ }
402
+ ```
403
+
404
+ Check writer capacity before writing:
405
+
406
+ ```scala
407
+ import zio.blocks.streams.io.Writer
408
+
409
+ val w = Writer.single[Int]
410
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@35d64778
411
+ println(w.writeable()) // true
412
+ // true
413
+ w.write(42)
414
+ // res30: Boolean = true
415
+ println(w.writeable()) // false (closed after accepting one)
416
+ // false
417
+ ```
418
+
419
+ ## Composition
420
+
421
+ Writers can be concatenated to chain multiple sinks together, or transformed to adapt their input types:
422
+
423
+ ### Concatenation
424
+
425
+ `Writer#concat` — Returns a `Writer` that writes to `this` until it closes, then transparently switches to `next`. If `this` closes with an error, the error is propagated immediately without consulting `next`. The dual of `Reader#concat`:
426
+
427
+ ```scala
428
+ abstract class Writer[-Elem] {
429
+ def concat[Elem1 <: Elem](next: => Writer[Elem1]): Writer[Elem1]
430
+ }
431
+ ```
432
+
433
+ `Writer#++` — Alias for `Writer#concat`. Syntactic sugar for composing writers:
434
+
435
+ ```scala
436
+ abstract class Writer[-Elem] {
437
+ def ++[Elem1 <: Elem](next: => Writer[Elem1]): Writer[Elem1]
438
+ }
439
+ ```
440
+
441
+ Here is how concatenation switches to the next writer when the first closes:
442
+
443
+ ```scala
444
+ import zio.blocks.streams.io.Writer
445
+ import scala.collection.mutable
446
+
447
+ val collected = mutable.ArrayBuffer[Int]()
448
+ // collected: ArrayBuffer[Int] = ArrayBuffer(5, 200)
449
+ val w1 = new Writer[Int] {
450
+ def isClosed = false
451
+ def write(a: Int) = {
452
+ if (a < 10) { collected += a; true; }
453
+ else false
454
+ }
455
+ def close() = ()
456
+ }
457
+ // w1: Writer[Int] = repl.MdocSession$MdocApp32$$anon$43@3bcbf5bd
458
+
459
+ val w2 = new Writer[Int] {
460
+ def isClosed = false
461
+ def write(a: Int) = { collected += a * 10; true }
462
+ def close() = ()
463
+ }
464
+ // w2: Writer[Int] = repl.MdocSession$MdocApp32$$anon$45@3ed3f62
465
+
466
+ val combined = w1 ++ w2
467
+ // combined: Writer[Int] = zio.blocks.streams.io.Writer$ConcatWith@6f75d7cc
468
+ combined.write(5)
469
+ // res33: Boolean = true
470
+ combined.write(20) // first writer rejects, switches to second
471
+ // res34: Boolean = true
472
+ println(collected.toList) // List(5, 200)
473
+ // List(5, 200)
474
+ ```
475
+
476
+ ### Transformation
477
+
478
+ `Writer#contramap` — Returns a `Writer` that transforms incoming elements with `g` before passing them to this writer. All other operations (`Writer#isClosed`, `Writer#close`, `Writer#fail`) delegate unchanged:
479
+
480
+ ```scala
481
+ abstract class Writer[-Elem] {
482
+ def contramap[Elem2](g: Elem2 => Elem): Writer[Elem2]
483
+ }
484
+ ```
485
+
486
+ Transform the input type before writing:
487
+
488
+ ```scala
489
+ import zio.blocks.streams.io.Writer
490
+
491
+ val stringWriter = new Writer[String] {
492
+ def isClosed = false
493
+ def write(a: String) = { println(s"Writing: $a"); true }
494
+ def close() = ()
495
+ }
496
+ // stringWriter: Writer[String] = repl.MdocSession$MdocApp36$$anon$51@23d463e2
497
+
498
+ val intWriter = stringWriter.contramap[Int](_.toString)
499
+ // intWriter: Writer[Int] = zio.blocks.streams.io.Writer$Contramapped@254fb1b1
500
+ intWriter.write(42) // Prints: Writing: 42
501
+ // Writing: 42
502
+ // res37: Boolean = true
503
+ ```
504
+
505
+ ## Closure and Error Handling
506
+
507
+ Writers support both clean closure and error closure, allowing you to signal end-of-stream gracefully or with an error condition:
508
+
509
+ ### Clean Closure
510
+
511
+ `Writer#close` — Closes the writer cleanly. After this call, `write()` returns `false` and `Writer#isClosed` returns `true`. Idempotent:
512
+
513
+ ```scala
514
+ abstract class Writer[-Elem] {
515
+ def close(): Unit
516
+ }
517
+ ```
518
+
519
+ ### Error Closure
520
+
521
+ `Writer#fail` — Closes the writer with an error. After this call, `Writer#isClosed` returns `true`. Subclasses that override this method may cause `write()` to throw `error` on subsequent calls; the default simply delegates to `Writer#close`. Both `Writer#close` and `Writer#fail` are idempotent; only the first call wins:
522
+
523
+ ```scala
524
+ abstract class Writer[-Elem] {
525
+ def fail(error: Throwable): Unit
526
+ }
527
+ ```
528
+
529
+ Close a writer with an error:
530
+
531
+ ```scala
532
+ import zio.blocks.streams.io.Writer
533
+
534
+ val w = Writer.single[Int]
535
+ // w: Writer[Int] = zio.blocks.streams.io.Writer$SingleWriter@7e886598
536
+ w.write(42)
537
+ // res39: Boolean = true
538
+ w.fail(new RuntimeException("Error"))
539
+ println(w.isClosed) // true
540
+ // true
541
+ ```
542
+
543
+ ## Contravariance
544
+
545
+ `Writer` is **contravariant** in `Elem`, meaning `Writer[-Elem]` can accept narrower types. If you have a `Writer[Number]`, you can use it as a `Writer[Int]` because every `Int` is a `Number`:
546
+
547
+ ```scala
548
+ import zio.blocks.streams.io.Writer
549
+
550
+ trait Number
551
+ case class IntNum(value: Int) extends Number
552
+
553
+ val numberWriter = new Writer[Number] {
554
+ def isClosed = false
555
+ def write(a: Number) = { println(s"Number: $a"); true }
556
+ def close() = ()
557
+ }
558
+ // numberWriter: Writer[Number] = repl.MdocSession$MdocApp42$$anon$59@290eac28
559
+
560
+ // numberWriter is also a Writer[IntNum] due to contravariance
561
+ val intNumWriter: Writer[IntNum] = numberWriter
562
+ // intNumWriter: Writer[IntNum] = repl.MdocSession$MdocApp42$$anon$59@290eac28
563
+ intNumWriter.write(IntNum(42))
564
+ // Number: IntNum(42)
565
+ // res43: Boolean = true
566
+ ```
567
+
568
+ This is the dual of Reader's covariance: Reader is covariant (`+Elem`) because narrower elements flow out; Writer is contravariant (`-Elem`) because broader element types flow in.
569
+
570
+ ## Integration with Readers and Channels
571
+
572
+ While Reader is typically used with pull-based stream operations, Writer is used internally by channel-based implementations and as an I/O adapter. The pairing is natural: a Reader pulls from a source, while a Writer pushes to a sink.
573
+
574
+ For typical stream usage, you'll see Writer indirectly when writing to files, network sockets, or other I/O resources. The `Writer.fromOutputStream` and `Writer.fromWriter` factories adapt standard Java I/O to the Writer interface.
575
+
576
+ ## Implementation Notes
577
+
578
+ Understanding `Writer`'s design decisions helps you use it correctly and avoid common pitfalls:
579
+
580
+ ### Push vs Pull
581
+
582
+ `Writer` is push-based (producer-driven), contrasting with `Reader` which is pull-based (consumer-driven):
583
+
584
+ | Aspect | Reader | Writer |
585
+ |----------------|----------------------------|-------------------------|
586
+ | **Direction** | Source → Consumer (pull) | Producer → Sink (push) |
587
+ | **Variance** | Covariant (`+Elem`) | Contravariant (`−Elem`) |
588
+ | **Blocking** | `read()` may block | `write()` may block |
589
+ | **Signal end** | Returns sentinel or `null` | `close()` or `fail()` |
590
+ | **Dual** | Sink drains Reader | Producer feeds Writer |
591
+
592
+ ### Thread Safety
593
+
594
+ `Writer` is **not thread-safe** by default. It is designed for single-threaded, push-based production. Do not share a `Writer` across threads without external synchronization. If you need concurrent production, wrap the writer in a thread-safe queue or use a concurrent streaming library.
595
+
596
+ ### Idempotency
597
+
598
+ Both `close()` and `fail()` are idempotent: only the first call wins. Subsequent calls have no effect. This simplifies error handling in try-finally blocks.
599
+
600
+ ## Running the Examples
601
+
602
+ All code from this guide is available as runnable examples in the `streams-examples` module.
603
+
604
+ **1. Clone the repository and navigate to the project:**
605
+
606
+ Run these commands to set up the examples:
607
+
608
+ ```bash
609
+ git clone https://github.com/zio/zio-blocks.git
610
+ cd zio-blocks
611
+ ```
612
+
613
+ **2. Run individual examples with sbt:**
614
+
615
+ ### Basic Writer Construction
616
+
617
+ This example demonstrates the most common writer factories: `Writer.single`, `Writer.limited`, `Writer.closed`, and custom writers via subclassing:
618
+
619
+ ```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
+ package writer
637
+
638
+ import zio.blocks.streams.io.Writer
639
+ import zio.blocks.chunk.Chunk
640
+ import scala.collection.mutable
641
+
642
+ /**
643
+ * Demonstrates the most common Writer factories: single, limited, closed, and
644
+ * custom writers via subclassing. Each writer is fed manually with write() to
645
+ * show how to produce elements.
646
+ */
647
+ object WriterBasicConstructionExample extends App {
648
+
649
+ println("=== Writer.single ===")
650
+ val singleWriter = Writer.single[Int]
651
+ println(s"Write 42: ${singleWriter.write(42)}")
652
+ println(s"Write 99 (closed): ${singleWriter.write(99)}")
653
+ println(s"isClosed: ${singleWriter.isClosed}")
654
+
655
+ println("\n=== Writer.closed ===")
656
+ val closedWriter = Writer.closed
657
+ println(s"Write to closed: ${closedWriter.write(1)}")
658
+ println(s"isClosed: ${closedWriter.isClosed}")
659
+
660
+ println("\n=== Writer.limited ===")
661
+ val limitedWriter = Writer.limited(Writer.single[String], 2)
662
+ val a = "a"
663
+ val b = "b"
664
+ val c = "c"
665
+ println(s"Write 'a': ${limitedWriter.write(a)}")
666
+ println(s"Write 'b': ${limitedWriter.write(b)}")
667
+ println(s"Write 'c': ${limitedWriter.write(c)}")
668
+ println(s"isClosed: ${limitedWriter.isClosed}")
669
+
670
+ println("\n=== writeAll: bulk write ===")
671
+ val collected = mutable.ArrayBuffer[Int]()
672
+ val collectWriter = new Writer[Int] {
673
+ def isClosed = false
674
+ def write(a: Int) = { collected += a; true }
675
+ def close(): Unit = ()
676
+ }
677
+
678
+ val chunk = Chunk(10, 20, 30)
679
+ val remaining = collectWriter.writeAll(chunk)
680
+ println(s"Collected: ${collected.toList}")
681
+ println(s"Remaining: $remaining")
682
+
683
+ println("\n=== writeable: check capacity ===")
684
+ val capWriter = Writer.single[Int]
685
+ println(s"writeable before: ${capWriter.writeable()}")
686
+ capWriter.write(42)
687
+ println(s"writeable after: ${capWriter.writeable()}")
688
+
689
+ println("\n=== Custom Writer ===")
690
+ val upperWriter = new Writer[String] {
691
+ private val buffer = mutable.ArrayBuffer[String]()
692
+ def isClosed = false
693
+ def write(a: String) = {
694
+ buffer += a.toUpperCase()
695
+ true
696
+ }
697
+ def close(): Unit = println(s"Final buffer: $buffer")
698
+ }
699
+
700
+ upperWriter.write("hello")
701
+ upperWriter.write("world")
702
+ upperWriter.close()
703
+ }
704
+ ```
705
+
706
+ Run this example with:
707
+
708
+ ```bash
709
+ sbt "streams-examples/runMain writer.WriterBasicConstructionExample"
710
+ ```
711
+
712
+ ### Composition and Transformation
713
+
714
+ This example shows writer composition with `Writer#++` (concat), transformation with `Writer#contramap`, and bulk writes with `Writer#writeAll`:
715
+
716
+ ```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
+ package writer
734
+
735
+ import zio.blocks.streams.io.Writer
736
+ import zio.blocks.chunk.Chunk
737
+ import scala.collection.mutable
738
+
739
+ /**
740
+ * Demonstrates writer composition with ++ (concat), transformation with
741
+ * contramap, and error handling via fail(). Shows how multiple writers can be
742
+ * chained and how transformations are applied before writing.
743
+ */
744
+ object WriterCompositionExample extends App {
745
+
746
+ println("=== concat: ++ operator ===")
747
+ val results = mutable.ArrayBuffer[Int]()
748
+
749
+ val w1 = new Writer[Int] {
750
+ def isClosed = false
751
+ def write(a: Int) = {
752
+ results += a * 10
753
+ a < 50 // reject values >= 50
754
+ }
755
+ def close(): Unit = ()
756
+ }
757
+
758
+ val w2 = new Writer[Int] {
759
+ def isClosed = false
760
+ def write(a: Int) = {
761
+ results += a * 100
762
+ true
763
+ }
764
+ def close(): Unit = ()
765
+ }
766
+
767
+ val combined = w1 ++ w2
768
+
769
+ combined.write(10) // accepted by w1 (10 < 50)
770
+ combined.write(60) // rejected by w1, switches to w2
771
+ println(s"Results: ${results.toList}")
772
+
773
+ println("\n=== contramap: transform elements ===")
774
+ val stringResults = mutable.ArrayBuffer[String]()
775
+ val stringWriter = new Writer[String] {
776
+ def isClosed = false
777
+ def write(a: String) = {
778
+ stringResults += a
779
+ true
780
+ }
781
+ def close(): Unit = ()
782
+ }
783
+
784
+ val intWriter = stringWriter.contramap[Int](_.toString)
785
+ intWriter.write(42)
786
+ intWriter.write(99)
787
+ println(s"String results: ${stringResults.toList}")
788
+
789
+ println("\n=== Multiple contramap: chained transformations ===")
790
+ val doubleResults = mutable.ArrayBuffer[String]()
791
+ val doubleStringWriter = new Writer[String] {
792
+ def isClosed = false
793
+ def write(a: String) = {
794
+ doubleResults += a
795
+ true
796
+ }
797
+ def close(): Unit = ()
798
+ }
799
+
800
+ val intDoubleWriter = doubleStringWriter
801
+ .contramap[Double](d => s"${d * 2}")
802
+ .contramap[Int](i => i.toDouble)
803
+
804
+ intDoubleWriter.write(5) // 5 -> 5.0 -> "10.0"
805
+ intDoubleWriter.write(10) // 10 -> 10.0 -> "20.0"
806
+ println(s"Double results: ${doubleResults.toList}")
807
+
808
+ println("\n=== fail: error closure ===")
809
+ val failWriter = new Writer[Int] {
810
+ private var closed = false
811
+ def isClosed = closed
812
+ def write(a: Int) =
813
+ if (closed) false else { println(s"Write: $a"); true }
814
+ def close(): Unit = closed = true
815
+ override def fail(error: Throwable): Unit = {
816
+ closed = true
817
+ println(s"Failed with: ${error.getMessage}")
818
+ }
819
+ }
820
+
821
+ failWriter.write(1)
822
+ failWriter.fail(new RuntimeException("Oops"))
823
+ println(s"isClosed after fail: ${failWriter.isClosed}")
824
+
825
+ println("\n=== writeAll: bulk operations ===")
826
+ val bulkResults = mutable.ArrayBuffer[Int]()
827
+ val bulkWriter = Writer.limited(
828
+ new Writer[Int] {
829
+ def isClosed = false
830
+ def write(a: Int) = { bulkResults += a; true }
831
+ def close(): Unit = ()
832
+ },
833
+ 2
834
+ )
835
+
836
+ val chunk = Chunk(1, 2, 3, 4)
837
+ val unwritten = bulkWriter.writeAll(chunk)
838
+ println(s"Bulk results: ${bulkResults.toList}")
839
+ println(s"Unwritten: $unwritten")
840
+ }
841
+ ```
842
+
843
+ Run this example with:
844
+
845
+ ```bash
846
+ sbt "streams-examples/runMain writer.WriterCompositionExample"
847
+ ```
848
+
849
+ ### I/O Adapters
850
+
851
+ This example demonstrates I/O integration with `Writer.fromOutputStream` and `Writer.fromWriter` for streaming to files or character streams:
852
+
853
+ ```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
+ package writer
871
+
872
+ import zio.blocks.streams.io.Writer
873
+ import java.io.{ByteArrayOutputStream, StringWriter}
874
+
875
+ /**
876
+ * Demonstrates I/O integration with Writer via fromOutputStream and fromWriter.
877
+ * Shows how to write bytes to streams and characters to writers using the
878
+ * Writer interface.
879
+ */
880
+ object WriterIOAdapterExample extends App {
881
+
882
+ println("=== Writer.fromOutputStream ===")
883
+ val byteStream = new ByteArrayOutputStream()
884
+ val byteWriter = Writer.fromOutputStream(byteStream)
885
+
886
+ byteWriter.write(72.toByte) // 'H'
887
+ byteWriter.write(105.toByte) // 'i'
888
+ byteWriter.write(33.toByte) // '!'
889
+ byteWriter.close()
890
+
891
+ println(s"Output: ${byteStream.toString("UTF-8")}")
892
+
893
+ println("\n=== writeBytes: bulk byte write ===")
894
+ val byteStream2 = new ByteArrayOutputStream()
895
+ val byteWriter2 = Writer.fromOutputStream(byteStream2)
896
+
897
+ val message = "Hello".getBytes("UTF-8")
898
+ val bytesWritten = byteWriter2.writeBytes(message, 0, message.length)
899
+ byteWriter2.close()
900
+
901
+ println(s"Bytes written: $bytesWritten")
902
+ println(s"Output: ${byteStream2.toString("UTF-8")}")
903
+
904
+ println("\n=== Writer.fromWriter ===")
905
+ val charStream = new StringWriter()
906
+ val charWriter = Writer.fromWriter(charStream)
907
+
908
+ charWriter.write('H')
909
+ charWriter.write('e')
910
+ charWriter.write('l')
911
+ charWriter.write('l')
912
+ charWriter.write('o')
913
+ charWriter.close()
914
+
915
+ println(s"Output: ${charStream.toString}")
916
+
917
+ println("\n=== writeChar: individual character writes ===")
918
+ val charStream2 = new StringWriter()
919
+ val charWriter2 = Writer.fromWriter(charStream2)
920
+
921
+ val greeting = "Hi!"
922
+ for (c <- greeting) {
923
+ val result = charWriter2.writeChar(c)
924
+ println(s"Write '$c': $result")
925
+ }
926
+ charWriter2.close()
927
+
928
+ println(s"Output: ${charStream2.toString}")
929
+
930
+ println("\n=== Specialized numeric writes ===")
931
+ val charStream3 = new StringWriter()
932
+ val charWriter3 = Writer.fromWriter(charStream3)
933
+
934
+ // Note: These specialized methods require the writer to be typed to accept them
935
+ // For a demo, we'll just show the interface exists
936
+ println("Specialized write methods available:")
937
+ println(" - writeInt(value: Int)")
938
+ println(" - writeLong(value: Long)")
939
+ println(" - writeFloat(value: Float)")
940
+ println(" - writeDouble(value: Double)")
941
+ println(" - writeBoolean(value: Boolean)")
942
+ println(" - writeShort(value: Short)")
943
+
944
+ charWriter3.close()
945
+
946
+ println("\n=== Error handling in I/O ===")
947
+ val closedStream = new ByteArrayOutputStream()
948
+ closedStream.close()
949
+ val failingWriter = Writer.fromOutputStream(closedStream)
950
+
951
+ val writeResult = failingWriter.write(65.toByte) // 'A'
952
+ println(s"Write to closed stream: $writeResult")
953
+ println(s"Writer is closed: ${failingWriter.isClosed}")
954
+ }
955
+ ```
956
+
957
+ Run this example with:
958
+
959
+ ```bash
960
+ sbt "streams-examples/runMain writer.WriterIOAdapterExample"
961
+ ```
962
+
963
+ ### Bounded Implementation
964
+
965
+ 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
+
967
+ ```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
+ package writer
985
+
986
+ import zio.blocks.streams.io.Writer
987
+ import scala.collection.mutable
988
+
989
+ /**
990
+ * Demonstrates implementing a bounded Writer that auto-closes when capacity is
991
+ * reached. Shows how write() returns false only on closure, not on buffer
992
+ * fullness, and how writeable() reflects the closure state.
993
+ */
994
+ object WriterBoundedImplementationExample extends App {
995
+
996
+ class BoundedWriter[A](maxCapacity: Int) extends Writer[A] {
997
+ private val buffer = mutable.Buffer[A]()
998
+ private var closed = false
999
+
1000
+ def isClosed: Boolean = closed
1001
+
1002
+ def write(a: A): Boolean =
1003
+ if (closed) false
1004
+ else if (buffer.size < maxCapacity) {
1005
+ buffer += a
1006
+ true
1007
+ } else {
1008
+ // Buffer full: auto-close and reject
1009
+ closed = true
1010
+ false
1011
+ }
1012
+
1013
+ def close(): Unit = closed = true
1014
+
1015
+ override def fail(error: Throwable): Unit = close()
1016
+
1017
+ def contents: mutable.Buffer[A] = buffer
1018
+ }
1019
+
1020
+ println("=== Bounded Writer with auto-close ===")
1021
+ val bounded = new BoundedWriter[Int](3)
1022
+
1023
+ println(s"Write 10: ${bounded.write(10)}")
1024
+ println(s"Write 20: ${bounded.write(20)}")
1025
+ println(s"Write 30: ${bounded.write(30)}")
1026
+ println(s"Write 40 (buffer full, auto-closes): ${bounded.write(40)}")
1027
+ println(s"Write 50 (closed): ${bounded.write(50)}")
1028
+
1029
+ println(s"\nBuffer contents: ${bounded.contents}")
1030
+ println(s"Writer closed: ${bounded.isClosed}")
1031
+ println(s"Writeable: ${bounded.writeable()}")
1032
+
1033
+ println("\n=== Behavior summary ===")
1034
+ println("• write() returns true while space exists")
1035
+ println("• When buffer fills, write() auto-closes and returns false")
1036
+ println("• All subsequent write() calls return false (closure is permanent)")
1037
+ println("• writeable() reflects closure state, not buffer capacity")
1038
+ }
1039
+ ```
1040
+
1041
+ Run this example with:
1042
+
1043
+ ```bash
1044
+ sbt "streams-examples/runMain writer.WriterBoundedImplementationExample"
1045
+ ```