@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,653 @@
1
+ ---
2
+ id: index
3
+ title: "Streams"
4
+ ---
5
+
6
+ `zio.blocks.streams` is a **synchronous, pull-based** streaming library for **Scala 3** (and Scala 2.13) with typed errors, resource safety, and primitive specialization. Streams are lazy descriptions -- nothing executes until a terminal operation is called. All results are returned as `Either[E, Z]`, keeping error handling explicit and typed. The library has zero runtime dependencies beyond `zio.blocks.chunk` and `zio.blocks.scope`, and achieves zero-boxing on primitive element types (`Int`, `Long`, `Float`, `Double`) through JVM-type-specialized internal readers.
7
+
8
+ ZIO Blocks Streams is built on three composable primitives:
9
+
10
+ | Type | Description | Key operation |
11
+ |----------------------------------------|----------------------------------------------------------------------|-----------------------|
12
+ | [`Stream[+E, +A]`](./stream.md) | A lazy, pull-based sequence of elements that may fail with error `E` | `stream.via(pipe)` |
13
+ | [`Pipeline[-In, +Out]`](./pipeline.md) | A reusable, composable stream-to-stream transformation | `pipe.andThen(other)` |
14
+ | [`Sink[+E, -A, +Z]`](./sink.md) | A stream consumer that produces a typed result `Z` | `stream.run(sink)` |
15
+
16
+ ## Overview
17
+
18
+ ZIO Blocks Streams is designed around three core principles:
19
+
20
+ **Synchronous execution.** All terminal operations (`run`, `runCollect`, `head`, etc.) return `Either[E, Z]` directly — no async effects, no ZIO runtime required. This makes streams easy to embed in any Scala or Java code.
21
+
22
+ **Pull-based evaluation.** Execution is driven from the consumer (Sink) backward through the pipeline to the source (Stream). This enables natural short-circuiting: if a sink only needs the first three elements, the stream stops producing after three elements — no work is wasted.
23
+
24
+ **Resource safety via RAII.** Resources acquired during stream construction (file handles, database connections, etc.) are always released in `finally` blocks, whether the stream succeeds, fails, or is short-circuited.
25
+
26
+ ## Quick Start
27
+
28
+ Here's a minimal example. Streams are lazy descriptions — nothing executes until you call a terminal operation like `runCollect`. The result is always `Either[E, Z]`, keeping errors explicit and typed.
29
+
30
+ ```scala
31
+ import zio.blocks.streams.*
32
+ import zio.blocks.chunk.Chunk
33
+
34
+ // Build a lazy stream description
35
+ val stream = Stream.range(1, 100)
36
+ .filter(_ % 2 == 0)
37
+ .map(_ * 3)
38
+
39
+ // Run it — nothing executes until here
40
+ val result = stream.take(5).runCollect
41
+ // Right(Chunk(6, 12, 18, 24, 30))
42
+ ```
43
+
44
+ ## Installation
45
+
46
+ Add the Streams module to your SBT build:
47
+
48
+ ```scala
49
+ libraryDependencies += "dev.zio" %% "zio-blocks-streams" % "0.0.51"
50
+ ```
51
+
52
+ For Scala.js (JavaScript/Node.js):
53
+
54
+ ```scala
55
+ libraryDependencies += "dev.zio" %%% "zio-blocks-streams" % "0.0.51"
56
+ ```
57
+
58
+ Supported Scala versions: 2.13.x and 3.x.
59
+
60
+ ## Why Streams?
61
+
62
+ Streaming libraries in the Scala ecosystem typically require an effect system. fs2 needs `cats.effect.IO`, Kyo Streams needs the Kyo runtime, and Pekko (formerly Akka) Streams needs the actor runtime. When your code is synchronous and you want streaming without pulling in an effect monad, the options narrow considerably.
63
+
64
+ `zio.blocks.streams` fills that gap:
65
+
66
+ | Feature | ZB Streams | fs2 | Kyo | Ox | Pekko |
67
+ |---------------------------|-------------------------|----------------------|---------------|-------------------------|-----------------|
68
+ | Effect system required | No | Yes (cats-effect) | Yes (Kyo) | No (virtual threads) | Yes (Akka) |
69
+ | Execution model | Synchronous, pull-based | Async, pull-based | Async, chunk | Synchronous, pull-based | Async, push |
70
+ | Typed errors | `Either[E, Z]` | ApplicativeError | Kyo effects | Exceptions | No |
71
+ | Primitive specialization | Yes (zero boxing) | No | No | No | No |
72
+ | Stack-safe deep pipelines | Yes (trampolined) | Yes (Pull) | Yes | No (SO on deep flatMap) | N/A |
73
+ | Resource safety | Scope integration | Resource/bracket | Kyo resources | try/finally | Graph lifecycle |
74
+ | Dependencies | chunk + scope | cats-effect + scodec | Kyo core | Ox core | Akka actor |
75
+
76
+ ## Benchmarks
77
+
78
+ All benchmarks use 10,000 elements, measured in operations per second (higher is better). Run on Apple M-series, JDK 25, Scala 3.7.4.
79
+
80
+ If you are evaluating Scala 2 compatibility work, read the [Scala 2 compatibility design note](./scala-2-compatibility.md) before moving any `Stream` or `Sink` hot-path combinators behind version-specific seams.
81
+
82
+ | Benchmark | ZB Streams | Ox | Kyo | fs2 | Pekko |
83
+ |----------------------|------------|--------|--------|--------|-------|
84
+ | drain | 179,872 | 54,512 | 31,777 | 20,795 | 4,381 |
85
+ | map | 161,920 | 42,007 | 12,012 | 13,295 | 2,259 |
86
+ | filter | 168,541 | 47,933 | 19,962 | 14,977 | 2,901 |
87
+ | flatMap | 49,165 | 30,506 | 28,303 | 748 | 742 |
88
+ | take/drop | 322,470 | 28,708 | 64,640 | 28,836 | 2,379 |
89
+ | map+filter+flatMap | 980 | 508 | 602 | 19 | 16 |
90
+ | mixed depth 1 | 47,459 | 19,449 | 13,427 | 257 | 639 |
91
+ | mixed depth 2 | 33,859 | 15,336 | 7,328 | 208 | 459 |
92
+ | mixed depth 3 | 23,610 | 11,878 | 3,174 | 139 | 256 |
93
+ | nested flatMap (10K) | 8,161 | -- | -- | 937 | -- |
94
+ | nested concat (10K) | 6,140 | -- | 3 | 1,065 | 1 |
95
+
96
+ "--" indicates the benchmark was not run or the library crashed.
97
+
98
+ ZB Streams leads in every single-operator benchmark and maintains its advantage as pipeline depth increases. The "mixed depth" rows show cascading `map`/`filter`/`flatMap` stages — ZB Streams degrades gracefully thanks to its trampolined execution model, while libraries without stack-safety (Ox) or with high per-element overhead (fs2, Pekko) fall off sharply.
99
+
100
+ ## Core mental model
101
+
102
+ To understand ZIO Blocks Streams fully, it's helpful to see how the three primitives fit together and how data flows through a pipeline from source to sink. This section walks through the architecture and explains each component in depth.
103
+
104
+ ### Execution Flow
105
+
106
+ Operations on streams transform the pipeline and ultimately run it against a sink:
107
+
108
+ ```
109
+ ┌──────────────────────────────────┐
110
+ │ Stream[E, A] │
111
+ │ (lazy description) │
112
+ └──────────────────┬───────────────┘
113
+ │
114
+ .flatMap, .map, .filter, etc.
115
+ │
116
+ ┌──────────────────▼───────────────┐
117
+ │ Pipeline[-In, +Out] │
118
+ │ (stream → stream transformation) │
119
+ └──────────────────┬───────────────┘
120
+ │
121
+ .via(pipe)
122
+ │
123
+ ┌──────────────────▼───────────────┐
124
+ │ Sink[E, A, Z] │
125
+ │ (stream consumer → result Z) │
126
+ └──────────────────┬───────────────┘
127
+ │
128
+ .run(sink)
129
+ │
130
+ ┌──────────────────▼───────────────┐
131
+ │ Either[E, Z] │
132
+ │ (synchronous result) │
133
+ └──────────────────────────────────┘
134
+ ```
135
+
136
+
137
+ ### 1) `Stream[E, A]` -- a lazy sequence
138
+
139
+ A `Stream[+E, +A]` is a **description** of a potentially infinite sequence of elements of type `A` that may fail with an error of type `E`. It is covariant in both type parameters.
140
+
141
+ Nothing happens when you construct a stream or chain transformations. Execution only begins when you call a terminal operation (`run`, `runCollect`, `runDrain`, `head`, `count`, etc.). Terminal operations return `Either[E, Z]`:
142
+
143
+ - `Left(e)` -- a typed stream error
144
+ - `Right(z)` -- the successful result
145
+
146
+ Untyped defects (unexpected exceptions) propagate as thrown exceptions, not as `Left` values.
147
+
148
+ ```scala
149
+ import zio.blocks.streams.*
150
+
151
+ // This does nothing -- it's just a description
152
+ val description: Stream[Nothing, Int] =
153
+ Stream.range(0, 1_000_000)
154
+ .filter(_ % 7 == 0)
155
+ .map(_ * 2)
156
+ .take(100)
157
+
158
+ // Only this line executes the pipeline
159
+ // val result = description.runCollect
160
+ ```
161
+
162
+ Streams render their pipeline structure as a human-readable string:
163
+
164
+ ```scala
165
+ val s = Stream.range(0, 100).map(_ + 1).filter(_ > 50).take(10)
166
+ println(s) // Stream.range(0, 100).map(...).filter(...).take(10)
167
+ ```
168
+
169
+ This makes debugging and logging straightforward -- you can see exactly what transformations a stream applies without running it.
170
+
171
+ ---
172
+
173
+ ### 2) `Sink[E, A, Z]` -- a consumer
174
+
175
+ A `Sink[+E, -A, +Z]` consumes elements of type `A` from a stream and produces a final result of type `Z`. Sinks are passed to `Stream.run`:
176
+
177
+ ```scala
178
+ import zio.blocks.streams.*
179
+
180
+ val streamSinks = Stream.range(1, 101)
181
+
182
+ // Built-in sinks
183
+ val total = streamSinks.run(Sink.count)
184
+ val items = streamSinks.run(Sink.collectAll)
185
+ val sum = streamSinks.run(Sink.sumInt)
186
+ val first = streamSinks.run(Sink.head)
187
+ ```
188
+
189
+ Most sinks also have convenience methods directly on `Stream`:
190
+
191
+ ```scala
192
+ stream.count // Either[Nothing, Long]
193
+ stream.runCollect // Either[Nothing, Chunk[Int]]
194
+ stream.head // Either[Nothing, Option[Int]]
195
+ stream.last // Either[Nothing, Option[Int]]
196
+ ```
197
+
198
+ Sinks compose with `contramap` (pre-process input) and `map` (post-process result):
199
+
200
+ ```scala
201
+ val lengthSink: Sink[Nothing, String, Long] =
202
+ Sink.sumInt.contramap[String](_.length)
203
+
204
+ val doubled: Sink[Nothing, Int, Long] =
205
+ Sink.sumInt.map(_ * 2)
206
+ ```
207
+
208
+ ---
209
+
210
+ ### 3) `Pipeline[In, Out]` -- reusable transformation
211
+
212
+ A `Pipeline[-In, +Out]` is a reusable stream transformation. It decouples the transformation logic from any specific stream, so you can define it once and apply it many times.
213
+
214
+ ```scala
215
+ // Define a reusable pipeline
216
+ val normalize: Pipeline[Int, Double] =
217
+ Pipeline.filter[Int](_ > 0)
218
+ .andThen(Pipeline.map[Int, Double](_.toDouble / 100.0))
219
+
220
+ // Apply to different streams
221
+ val result1 = Stream.range(-10, 10).via(normalize).runCollect
222
+ val result2 = Stream.fromIterable(List(42, -5, 100, 0)).via(normalize).runCollect
223
+ ```
224
+
225
+ Pipelines compose with `andThen`:
226
+
227
+ ```scala
228
+ val step1: Pipeline[String, Int] =
229
+ Pipeline.map[String, Int](_.length)
230
+
231
+ val step2: Pipeline[Int, Int] =
232
+ Pipeline.filter[Int](_ > 3)
233
+
234
+ val combined: Pipeline[String, Int] =
235
+ step1.andThen(step2)
236
+ ```
237
+
238
+ You can also apply a pipeline to a sink with `andThenSink` / `applyToSink`, which pre-processes the sink's input:
239
+
240
+ ```scala
241
+ val countLong: Sink[Nothing, String, Long] =
242
+ Pipeline.map[String, Int](_.length)
243
+ .andThenSink(Sink.sumInt)
244
+ ```
245
+
246
+ ## Error Handling
247
+
248
+ Streams distinguish between two kinds of failures:
249
+
250
+ - **Typed errors** (`E`) — domain errors you expect and handle, returned as `Left` in the result. Use `catchAll`, `orElse`, or `mapError` to recover.
251
+ - **Defects** (`Throwable`) — unexpected exceptions from bugs or system failures. Use `catchDefect` to recover, or they propagate as thrown exceptions.
252
+
253
+ ```scala
254
+ val failing: Stream[String, Int] =
255
+ Stream.fromIterable(List(1, 2, 3)) ++ Stream.fail("oops") ++ Stream.fromIterable(List(4, 5))
256
+
257
+ val recovered = failing.catchAll(_ => Stream.fromIterable(List(99)))
258
+ recovered.runCollect // Right(Chunk(1, 2, 3, 99))
259
+ ```
260
+
261
+ ## Resource Management
262
+
263
+ Streams integrate with `zio.blocks.scope.Scope` for deterministic resource cleanup. The `fromAcquireRelease` constructor guarantees that a resource is acquired lazily (when the stream runs), used to produce elements, and then released — even if the stream is short-circuited early via `take()`, fails with an error, or succeeds normally. The release function is wired into a finally block, ensuring cleanup always happens.
264
+
265
+ ```scala
266
+ import zio.blocks.streams.*
267
+
268
+ val managed = Stream.fromAcquireRelease(
269
+ acquire = scala.io.Source.fromFile("data.txt"),
270
+ release = _.close()
271
+ )(source => Stream.fromIterator(source.getLines()))
272
+
273
+ managed.take(10).runCollect
274
+ // File is closed in finally block regardless of outcome
275
+ ```
276
+
277
+ This eliminates the need for manual try/finally when working with resources — the stream handles it for you.
278
+
279
+ ## Primitive Specialization
280
+
281
+ ZB Streams eliminates boxing for `Int`, `Long`, `Float`, and `Double` elements throughout the entire pipeline. Every intermediate step uses specialized `readInt`/`readLong`/`readFloat`/`readDouble` methods, so no `java.lang.Integer` wrappers are allocated.
282
+
283
+ ```scala
284
+ import zio.blocks.streams.*
285
+
286
+ // This entire pipeline runs with ZERO boxing of the Int elements.
287
+ // Every step uses specialized readInt/writeInt internally.
288
+ val sum: Either[Nothing, Long] =
289
+ Stream.range(0, 1_000_000) // Int-specialized source
290
+ .filter(_ % 2 == 0) // Int-specialized filter
291
+ .map(_ * 3) // Int->Int specialized map
292
+ .runFold(0L)(_ + _) // Long-specialized accumulator
293
+ ```
294
+
295
+ This matters most for numeric workloads — data processing, statistics, encoding/decoding — where millions of elements flow through multi-stage pipelines.
296
+
297
+ ## Practical Guidance
298
+
299
+ - **Start with `Stream` constructors and terminal operations.** You can get very far with `Stream.range`, `Stream.fromIterable`, `.map`, `.filter`, and `.runCollect`.
300
+ - **Use `Either` pattern matching** to handle the result: `Right(value)` for success, `Left(error)` for typed failures.
301
+ - **Prefer `Stream.fromAcquireRelease`** when wrapping resources (files, connections, etc.) over manual try/finally. It guarantees cleanup even on early termination via `take`, `head`, or error.
302
+ - **Use the auto-closing I/O constructors** (`fromInputStream`, `fromJavaReader`, `NioStreams.fromChannel`) by default. Only use the `Unmanaged` variants when you need to borrow a resource whose lifetime is managed elsewhere.
303
+ - **Use `Pipeline`** when you have a transformation you want to reuse across multiple streams or apply to sinks.
304
+ - **Use `&&` for zipping** instead of manual zip calls. Tuples flatten automatically: `a && b && c` produces `(A, B, C)` not `((A, B), C)`.
305
+ - **Leverage primitive specialization** for numeric workloads. Streams of `Int`, `Long`, `Float`, and `Double` avoid boxing automatically; use `Sink.sumInt`, `runFold(0)(_ + _)`, etc. for zero-allocation folds.
306
+ - **Use `scan` for running accumulators**, `grouped` for batching, and `sliding` for windowed computations.
307
+ - **Use `render`/`toString`** to inspect pipeline structure during debugging — it shows each transformation stage without executing the stream.
308
+ - **Use `Sink.create`** as an escape hatch when none of the built-in sinks fit.
309
+ - **`suspend`** is your friend for recursive or self-referential stream definitions, preventing stack overflow during construction.
310
+ - **Typed errors vs. defects**: use `Stream.fail` for expected domain errors and `Stream.die` for programmer errors. Use `catchAll` for the former, `catchDefect` for the latter.
311
+
312
+ ## Usage examples
313
+
314
+ This section shows practical examples of using streams in real-world scenarios. Each subsection demonstrates a different aspect of the API with runnable code examples.
315
+
316
+ ### Creating streams
317
+
318
+ Here are the most common ways to construct a stream. Choose the constructor that best fits your data source:
319
+
320
+
321
+ ```scala
322
+ import zio.blocks.streams.*
323
+ import zio.blocks.chunk.Chunk
324
+
325
+ // From explicit elements
326
+ Stream.fromIterable(List(1, 2, 3)) // Stream[Nothing, Int]
327
+ Stream.fromIterable(List("a", "b", "c")) // Stream[Nothing, String]
328
+
329
+ // From collections
330
+ Stream.fromChunk(Chunk(1, 2, 3)) // Stream[Nothing, Int]
331
+ Stream.fromIterable(List("x", "y", "z")) // Stream[Nothing, String]
332
+ Stream.fromIterator(Iterator.from(1)) // Stream[Nothing, Int] (lazy)
333
+
334
+ // Ranges
335
+ Stream.range(0, 100) // 0 to 99
336
+ Stream.fromRange(1 to 50) // 1 to 50
337
+
338
+ // Single values (primitive-specialized)
339
+ Stream.succeed(42) // Stream[Nothing, Int]
340
+ Stream.succeed(3.14) // Stream[Nothing, Double]
341
+ Stream.succeed("hello") // Stream[Nothing, String]
342
+
343
+ // Special streams
344
+ Stream.empty // Stream[Nothing, Nothing]
345
+ Stream.fail("error") // Stream[String, Nothing]
346
+ // Stream.die(new Exception("defect")) // throws on evaluation
347
+
348
+ // Generators
349
+ Stream.repeat(1) // infinite stream of 1s
350
+ // Stream.iterate(1)(_ * 2) // 1, 2, 4, 8, 16, ...
351
+ // Stream.repeatThunk(scala.util.Random.nextInt(100)) // infinite random ints
352
+ Stream.unfold(0)(n => // 0, 1, 2, ..., 9
353
+ if n < 10 then Some((n, n + 1)) else None
354
+ )
355
+
356
+ // Side-effects
357
+ Stream.eval(println("hello")) // prints, emits nothing
358
+ Stream.attempt(someFallibleCall()) // captures exceptions as typed errors
359
+ Stream.attemptEval(riskyEffect()) // same, for Unit-returning effects
360
+
361
+ // Deferred construction (useful for recursion)
362
+ Stream.suspend(expensiveStreamBuilder())
363
+
364
+ // I/O sources (auto-closing) - JVM only
365
+ Stream.fromInputStream(inputStream) // Stream[IOException, Int] (bytes as 0-255, auto-closes)
366
+ Stream.fromJavaReader(javaReader) // Stream[IOException, Char] (auto-closes)
367
+
368
+ // I/O sources (borrowing -- caller manages lifetime) - JVM only
369
+ Stream.fromInputStreamUnmanaged(inputStream) // Stream[IOException, Int] (does NOT close)
370
+ Stream.fromJavaReaderUnmanaged(javaReader) // Stream[IOException, Char] (does NOT close)
371
+ ```
372
+
373
+ ---
374
+
375
+ ### Transforming streams
376
+
377
+ Streams support many transformation operations. Use `map` for element-wise changes, `filter` for selection, and `flatMap` for expanding elements into sub-streams. See the [Stream reference](./stream.md) page for comprehensive examples of all transformation methods including `map`, `filter`, `flatMap`, `collect`, `scan`, `mapAccum`, `distinct`, `intersperse`, and more.
378
+
379
+ ---
380
+
381
+ ### Zipping streams with `&&`
382
+
383
+ The `&&` operator zips two streams element-by-element into tuples. The resulting stream ends when either input is exhausted.
384
+
385
+ ```scala
386
+ import zio.blocks.streams.*
387
+
388
+ val names: Stream[Nothing, String] = Stream.fromIterable(List("Alice", "Bob", "Charlie"))
389
+ val ages: Stream[Nothing, Int] = Stream.fromIterable(List(30, 25, 35))
390
+ val ids: Stream[Nothing, Long] = Stream.fromIterable(List(1L, 2L, 3L))
391
+
392
+ // Two-way zip
393
+ val pairs = names && ages
394
+ pairs.runCollect // Right(Chunk(("Alice", 30), ("Bob", 25), ("Charlie", 35)))
395
+
396
+ // Three-way zip -- tuples flatten automatically
397
+ val triples = names && ages && ids
398
+ triples.runCollect // Right(Chunk(("Alice", 30, 1L), ("Bob", 25, 2L), ("Charlie", 35, 3L)))
399
+ ```
400
+
401
+ When the error types differ, they widen via union:
402
+
403
+ ```scala
404
+ import zio.blocks.streams.*
405
+
406
+ sealed trait MyError
407
+ val s1: Stream[MyError, Int] = Stream.fromIterable(List(1, 2, 3))
408
+ sealed trait OtherError
409
+ val s2: Stream[OtherError, Int] = Stream.fromIterable(List(4, 5, 6))
410
+ // val zipped = s1 && s2
411
+ ```
412
+
413
+ ---
414
+
415
+ ### Primitive specialization
416
+
417
+ ZB Streams eliminates boxing for `Int`, `Long`, `Float`, and `Double` elements throughout the entire pipeline. Every intermediate step uses specialized `readInt`/`writeInt` (or the corresponding type) methods, so no `java.lang.Integer` wrappers are allocated.
418
+
419
+ ```scala
420
+ // This entire pipeline runs with ZERO boxing of the Int elements.
421
+ // Every step uses specialized readInt/writeInt internally.
422
+ val sum: Either[Nothing, Long] =
423
+ Stream.range(0, 1_000_000) // Int-specialized source
424
+ .filter(_ % 2 == 0) // Int-specialized filter
425
+ .map(_ * 3) // Int->Int specialized map
426
+ .runFold(0L)(_ + _) // Long-specialized accumulator
427
+ ```
428
+
429
+ This matters most for numeric workloads -- data processing, statistics, encoding/decoding -- where millions of elements flow through multi-stage pipelines. The benchmark results above reflect this advantage directly.
430
+
431
+ ---
432
+
433
+ ### Consuming streams
434
+
435
+ Terminal operations run the stream and produce a final result. Use `runCollect` to gather all elements, `runDrain` to discard them, or specialized operations like `head`, `count`, and `foldLeft`:
436
+
437
+ ```scala
438
+ val s = Stream.range(1, 11) // 1 to 10
439
+
440
+ // Collect all elements
441
+ s.runCollect // Right(Chunk(1, 2, 3, ..., 10))
442
+
443
+ // Discard all elements (run for side-effects only)
444
+ s.tapEach(println).runDrain
445
+
446
+ // Fold
447
+ s.runFold(0)(_ + _) // Right(55) (Int accumulator)
448
+ s.runFold(0L)(_ + _) // Right(55L) (Long accumulator)
449
+ s.runFold(0.0)(_ + _) // Right(55.0) (Double accumulator)
450
+
451
+ // Foreach
452
+ s.runForeach(n => println(n))
453
+ s.foreach(n => println(n)) // alias
454
+
455
+ // Aggregates
456
+ s.count // Right(10L)
457
+ s.head // Right(Some(1))
458
+ s.last // Right(Some(10))
459
+ s.exists(_ > 5) // Right(true)
460
+ s.forall(_ > 0) // Right(true)
461
+ s.find(_ > 7) // Right(Some(8))
462
+
463
+ // Run with an explicit Sink
464
+ s.run(Sink.sumInt) // Right(55L)
465
+ s.run(Sink.take(3)) // Right(Chunk(1, 2, 3))
466
+ ```
467
+
468
+ ---
469
+
470
+ ### Error handling patterns
471
+
472
+ Streams support two types of failures: typed errors that you can handle explicitly, and defects (exceptions) that propagate. Here are common patterns for dealing with both:
473
+
474
+ ```scala
475
+ // Typed error: appears in Either
476
+ val result = Stream.fail("not found").runCollect
477
+ // result: Left("not found")
478
+
479
+ // Recover and continue
480
+ val safe =
481
+ Stream.fromIterable(List(1, 2)) ++ Stream.fail("oops") ++ Stream.fromIterable(List(3))
482
+ val recovered = safe.catchAll(_ => Stream.fromIterable(List(99))).runCollect
483
+ // Right(Chunk(1, 2, 99))
484
+
485
+ // Transform error type by catching and converting
486
+ val inputError: Stream[String, Int] = Stream.fail("bad input")
487
+ val transformed = inputError.catchAll(msg => Stream.fail(new IllegalArgumentException(msg)))
488
+
489
+ // Fallback stream
490
+ val primary: Stream[String, Int] = Stream.fail("down")
491
+ val backup: Stream[String, Int] = Stream.fromIterable(List(1, 2, 3))
492
+ val result2 = (primary || backup).runCollect
493
+ // Right(Chunk(1, 2, 3))
494
+
495
+ // Catch defects (unexpected exceptions)
496
+ val risky: Stream[Nothing, Int] =
497
+ Stream.fromIterable(List(1, 2, 3)).map { n =>
498
+ if n == 2 then throw new ArithmeticException("boom")
499
+ else n
500
+ }
501
+
502
+ val handled = risky.catchDefect {
503
+ case _: ArithmeticException => Stream.fromIterable(List(0))
504
+ }.runCollect
505
+ // Right(Chunk(1, 0))
506
+ ```
507
+
508
+ ---
509
+
510
+ ### Resource safety patterns
511
+
512
+ When working with files, network connections, or other resources, use the resource-safe constructors to guarantee cleanup. Here are the most common patterns:
513
+
514
+ ```scala
515
+ import zio.blocks.streams.*
516
+ import zio.blocks.scope.*
517
+
518
+ // Bracket pattern: acquire/use/release
519
+ def fileLines(path: String): Stream[Nothing, String] =
520
+ Stream.fromAcquireRelease(
521
+ acquire = scala.io.Source.fromFile(path),
522
+ release = _.close()
523
+ ) { source =>
524
+ Stream.fromIterable(source.getLines().toList)
525
+ }
526
+
527
+ // Compose resource-safe streams -- both resources are released
528
+ val merged =
529
+ fileLines("input1.txt") ++ fileLines("input2.txt")
530
+
531
+ // Only reads 10 lines; both files are still closed properly
532
+ merged.take(10).runCollect
533
+
534
+ // ensuring: attach a finalizer
535
+ var cleaned = false
536
+ Stream.range(1, 6)
537
+ .ensuring { cleaned = true }
538
+ .take(2)
539
+ .runDrain
540
+ // cleaned == true, even though only 2 of 5 elements were consumed
541
+
542
+ // defer: register cleanup that runs on stream close
543
+ val withDefer =
544
+ Stream.defer(println("releasing lock")) ++
545
+ Stream.range(1, 100)
546
+ ```
547
+
548
+ ---
549
+
550
+ ### NIO integration (JVM only)
551
+
552
+ On the JVM, `NioStreams` and `NioSinks` provide zero-copy integration with `java.nio` buffers and channels.
553
+
554
+ #### `NioStreams` -- creating streams from NIO sources
555
+
556
+ ```scala
557
+ import zio.blocks.streams.*
558
+ import java.nio.ByteBuffer
559
+ import java.nio.channels.FileChannel
560
+ import java.nio.file.{Paths, StandardOpenOption}
561
+
562
+ // From a ByteBuffer
563
+ val buf = ByteBuffer.wrap(Array[Byte](1, 2, 3, 4, 5))
564
+ NioStreams.fromByteBuffer(buf).runCollect
565
+ // Right(Chunk(1, 2, 3, 4, 5))
566
+
567
+ // Typed buffer views (zero-boxing)
568
+ val intBuf = ByteBuffer.allocate(16).putInt(1).putInt(2).putInt(3).putInt(4).flip()
569
+ NioStreams.fromByteBufferInt(intBuf).runCollect
570
+ // Right(Chunk(1, 2, 3, 4))
571
+
572
+ // Similarly: fromByteBufferLong, fromByteBufferFloat, fromByteBufferDouble
573
+
574
+ // From a ReadableByteChannel (auto-closing)
575
+ val ch = FileChannel.open(Paths.get("data.bin"), StandardOpenOption.READ)
576
+ val bytes = NioStreams.fromChannel(ch, bufSize = 4096).runCollect
577
+ // ch is closed automatically when the stream completes
578
+
579
+ // From a ReadableByteChannel (borrowing -- caller manages lifetime)
580
+ val ch2 = FileChannel.open(Paths.get("data.bin"), StandardOpenOption.READ)
581
+ val bytes2 = NioStreams.fromChannelUnmanaged(ch2, bufSize = 4096).runCollect
582
+ ch2.close() // caller is responsible for closing
583
+ ```
584
+
585
+ #### `NioSinks` -- writing to NIO targets
586
+
587
+ ```scala
588
+ import zio.blocks.streams.*
589
+ import zio.blocks.chunk.Chunk
590
+ import java.nio.ByteBuffer
591
+ import java.nio.channels.FileChannel
592
+ import java.nio.file.{Files, StandardOpenOption}
593
+
594
+ // Write to a ByteBuffer using a typed sink (Int values, zero-boxing)
595
+ val outBuf = ByteBuffer.allocate(1024)
596
+ Stream.range(1, 5).run(NioSinks.fromByteBufferInt(outBuf))
597
+
598
+ // Write to a WritableByteChannel using a stream of bytes
599
+ val tempPath = Files.createTempFile("zio-blocks-streams-", ".bin")
600
+ val outCh = FileChannel.open(
601
+ tempPath,
602
+ StandardOpenOption.WRITE,
603
+ StandardOpenOption.TRUNCATE_EXISTING
604
+ )
605
+ val bytes = Chunk.fromIterable(List[Byte](1, 2, 3, 4, 5))
606
+ try Stream.fromChunk(bytes).run(NioSinks.fromChannel(outCh))
607
+ finally {
608
+ outCh.close()
609
+ Files.deleteIfExists(tempPath)
610
+ }
611
+ ```
612
+
613
+ ---
614
+
615
+ ### Pipeline composition
616
+
617
+ Pipelines are composable transformations that can be reused across different streams. Build complex transformations by chaining pipelines together with `andThen`:
618
+
619
+ ```scala
620
+ import zio.blocks.streams.*
621
+
622
+ // Build reusable transformation steps
623
+ val parseInts: Pipeline[String, Int] =
624
+ Pipeline.collect[String, Int] {
625
+ case s if s.matches("-?\\d+") => s.toInt
626
+ }
627
+
628
+ val positiveOnly: Pipeline[Int, Int] =
629
+ Pipeline.filter[Int](_ > 0)
630
+
631
+ val doubled: Pipeline[Int, Int] =
632
+ Pipeline.map[Int, Int](_ * 2)
633
+
634
+ // Compose into a single pipeline
635
+ val fullPipeline: Pipeline[String, Int] =
636
+ parseInts
637
+ .andThen(positiveOnly)
638
+ .andThen(doubled)
639
+
640
+ // Apply to any stream of strings
641
+ Stream.fromIterable(List("10", "abc", "-3", "7", "0", "25"))
642
+ .via(fullPipeline)
643
+ .runCollect
644
+ // Right(Chunk(20, 14, 50))
645
+
646
+ // Apply to a sink (pre-process the sink's input)
647
+ val sumPositiveDoubled: Sink[Nothing, String, Long] =
648
+ fullPipeline.andThenSink(Sink.sumInt)
649
+
650
+ Stream.fromIterable(List("10", "abc", "-3", "7", "0", "25"))
651
+ .run(sumPositiveDoubled)
652
+ // Right(84L)
653
+ ```