@zio.dev/zio-blocks 0.0.33 → 0.0.55

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 (215) 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 +21 -16
  4. package/guides/getting-started-with-mux.md +1395 -0
  5. package/guides/query-dsl-extending.md +161 -102
  6. package/guides/query-dsl-fluent-builder.md +217 -157
  7. package/guides/query-dsl-reified-optics.md +12 -10
  8. package/guides/query-dsl-sql.md +640 -165
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +1130 -0
  12. package/guides/zio-schema-migration.md +29 -22
  13. package/index.md +248 -389
  14. package/package.json +1 -1
  15. package/plans/config-follow-up-prs.md +188 -0
  16. package/plans/config-pr-assessment-roadmap.md +310 -0
  17. package/reference/MuxDataFlow.jsx +250 -0
  18. package/reference/async.md +1499 -0
  19. package/reference/chunk.md +3533 -308
  20. package/reference/codegen/case-class.md +436 -0
  21. package/reference/codegen/emitter-config.md +383 -0
  22. package/reference/codegen/examples.md +664 -0
  23. package/reference/codegen/field.md +316 -0
  24. package/reference/codegen/index.md +317 -0
  25. package/reference/codegen/scala-emitter.md +392 -0
  26. package/reference/codegen/scala-file.md +276 -0
  27. package/reference/codegen/sealed-trait.md +408 -0
  28. package/reference/codegen/type-definition.md +340 -0
  29. package/reference/codegen/type-ref.md +201 -0
  30. package/reference/combinators.md +347 -117
  31. package/reference/config/config-decoder.md +460 -0
  32. package/reference/config/config-source.md +489 -0
  33. package/reference/config/errors.md +278 -0
  34. package/reference/config/flags.md +369 -0
  35. package/reference/config/formats.md +314 -0
  36. package/reference/config/index.md +304 -0
  37. package/reference/config/rollout.md +336 -0
  38. package/reference/context.md +9 -52
  39. package/reference/data-migration.md +269 -0
  40. package/reference/datastar/attributes.md +302 -0
  41. package/reference/datastar/events.md +234 -0
  42. package/reference/datastar/index.md +256 -0
  43. package/reference/datastar/signals.md +230 -0
  44. package/reference/datastar/sse.md +295 -0
  45. package/reference/datastar.md +346 -0
  46. package/reference/docs.md +1461 -345
  47. package/reference/endpoint/auth-type.md +146 -0
  48. package/reference/endpoint/bulk-creation.md +96 -0
  49. package/reference/endpoint/endpoint.md +297 -0
  50. package/reference/endpoint/http-codec.md +249 -0
  51. package/reference/endpoint/index.md +745 -0
  52. package/reference/endpoint/path-codec.md +225 -0
  53. package/reference/endpoint/route-pattern.md +194 -0
  54. package/reference/endpoint/route-tree.md +111 -0
  55. package/reference/endpoint/segment-codec.md +199 -0
  56. package/reference/html.md +1424 -0
  57. package/reference/htmx/attribute-values.md +359 -0
  58. package/reference/htmx/hx-encoding.md +111 -0
  59. package/reference/htmx/hx-params.md +204 -0
  60. package/reference/htmx/hx-swap.md +276 -0
  61. package/reference/htmx/hx-sync.md +251 -0
  62. package/reference/htmx/hx-target.md +314 -0
  63. package/reference/htmx/hx-trigger.md +457 -0
  64. package/reference/htmx/hx-url-update.md +239 -0
  65. package/reference/htmx/index.md +807 -0
  66. package/reference/htmx/response-headers.md +240 -0
  67. package/reference/http-model/headers.md +735 -0
  68. package/reference/http-model/index.md +49 -0
  69. package/reference/http-model/model.md +1517 -0
  70. package/reference/http-model/schema-codecs.md +522 -0
  71. package/reference/http-model/schema.md +750 -0
  72. package/reference/http-model/server-sent-event.md +341 -0
  73. package/reference/jwt.md +195 -0
  74. package/reference/maybe.md +943 -0
  75. package/reference/media-type.md +2 -2
  76. package/reference/mux.md +254 -0
  77. package/reference/mux.mdx +828 -0
  78. package/reference/openapi.md +1351 -0
  79. package/reference/projection.md +654 -0
  80. package/reference/resource-management/defer-handle.md +1 -1
  81. package/reference/resource-management/resource.md +31 -98
  82. package/reference/resource-management/scope.md +28 -220
  83. package/reference/resource-management/wire.md +5 -55
  84. package/reference/ringbuffer/MpmcDiagram.jsx +717 -0
  85. package/reference/ringbuffer/MpscDiagram.jsx +618 -0
  86. package/reference/ringbuffer/SpmcDiagram.jsx +680 -0
  87. package/reference/ringbuffer/SpscDiagram.jsx +677 -0
  88. package/reference/ringbuffer/advanced.mdx +109 -0
  89. package/reference/ringbuffer/index.mdx +145 -0
  90. package/reference/ringbuffer/mpmc.mdx +185 -0
  91. package/reference/ringbuffer/mpsc.mdx +164 -0
  92. package/reference/ringbuffer/spmc.mdx +108 -0
  93. package/reference/ringbuffer/spsc.mdx +416 -0
  94. package/reference/{allows.md → schema/allows.md} +4 -100
  95. package/reference/{binding-resolver.md → schema/binding-resolver.md} +1 -1
  96. package/reference/{binding.md → schema/binding.md} +3 -4
  97. package/reference/schema/built-in-codecs/avro.md +451 -0
  98. package/reference/schema/built-in-codecs/bson.md +510 -0
  99. package/reference/schema/built-in-codecs/csv.md +564 -0
  100. package/reference/schema/built-in-codecs/index.md +77 -0
  101. package/reference/schema/built-in-codecs/json/index.md +295 -0
  102. package/reference/schema/built-in-codecs/json/json-config.md +217 -0
  103. package/reference/{json-patch.md → schema/built-in-codecs/json/json-patch.md} +5 -5
  104. package/reference/{json-schema.md → schema/built-in-codecs/json/json-schema.md} +14 -47
  105. package/reference/schema/built-in-codecs/json/json-selection.md +322 -0
  106. package/reference/{json.md → schema/built-in-codecs/json/json.md} +32 -64
  107. package/reference/schema/built-in-codecs/messagepack.md +508 -0
  108. package/reference/schema/built-in-codecs/thrift.md +433 -0
  109. package/reference/schema/built-in-codecs/toon.md +1078 -0
  110. package/reference/{xml.md → schema/built-in-codecs/xml.md} +13 -9
  111. package/reference/schema/built-in-codecs/yaml.md +552 -0
  112. package/reference/{codec.md → schema/codec.md} +11 -11
  113. package/reference/{dynamic-optic.md → schema/dynamic-optic.md} +196 -5
  114. package/reference/{dynamic-schema.md → schema/dynamic-schema.md} +8 -8
  115. package/reference/schema/format.md +92 -0
  116. package/reference/schema/index.md +52 -0
  117. package/reference/schema/migration.md +297 -0
  118. package/reference/{modifier.md → schema/modifier.md} +58 -7
  119. package/reference/{optics.md → schema/optics.md} +2 -2
  120. package/reference/{patch.md → schema/patch.md} +1 -1
  121. package/{path-interpolator.md → reference/schema/path-interpolator.md} +167 -72
  122. package/reference/schema/reflect-transformer.md +140 -0
  123. package/reference/{schema-evolution → schema/schema-evolution}/as.md +8 -8
  124. package/reference/{schema-evolution → schema/schema-evolution}/index.md +2 -2
  125. package/reference/{schema-evolution → schema/schema-evolution}/into.md +8 -8
  126. package/reference/{schema-expr.md → schema/schema-expr.md} +110 -175
  127. package/reference/schema/schema-search.md +263 -0
  128. package/reference/{schema.md → schema/schema.md} +22 -2
  129. package/reference/{structural-types.md → schema/structural-types.md} +1 -1
  130. package/reference/{type-class-derivation.md → schema/type-class-derivation.md} +63 -1
  131. package/reference/smithy.md +1032 -0
  132. package/reference/sql/db-codec-deriver.md +71 -0
  133. package/reference/sql/db-codec.md +687 -0
  134. package/reference/sql/db-con.md +271 -0
  135. package/reference/sql/db-connection.md +153 -0
  136. package/reference/sql/db-param-writer.md +77 -0
  137. package/reference/sql/db-param.md +66 -0
  138. package/reference/sql/db-result-reader.md +148 -0
  139. package/reference/sql/db-tx.md +114 -0
  140. package/reference/sql/db-value.md +41 -0
  141. package/reference/sql/ddl.md +85 -0
  142. package/reference/sql/frag.md +288 -0
  143. package/reference/sql/index.md +341 -0
  144. package/reference/sql/repo.md +600 -0
  145. package/reference/sql/sql-dialect.md +73 -0
  146. package/reference/sql/sql-logger.md +62 -0
  147. package/reference/sql/sql-name-mapper.md +70 -0
  148. package/reference/sql/table-metadata.md +134 -0
  149. package/reference/sql/table.md +448 -0
  150. package/reference/sql/transactor-zio.md +399 -0
  151. package/reference/sql/transactor.md +363 -0
  152. package/reference/sql-zio.md +112 -0
  153. package/reference/streams/core/index.md +32 -0
  154. package/reference/streams/core/pipeline.md +854 -0
  155. package/reference/streams/core/sink.md +1404 -0
  156. package/reference/streams/core/stream.md +3236 -0
  157. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  158. package/reference/streams/execution-and-compatibility/index.md +35 -0
  159. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  160. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  161. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  162. package/reference/streams/index.md +726 -0
  163. package/reference/streams/primitives/index.md +30 -0
  164. package/reference/streams/primitives/reader.md +1992 -0
  165. package/reference/streams/primitives/writer.md +1201 -0
  166. package/reference/telemetry/common/any-value.md +90 -0
  167. package/reference/telemetry/common/attribute-key.md +87 -0
  168. package/reference/telemetry/common/attributes.md +118 -0
  169. package/reference/telemetry/common/index.md +39 -0
  170. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  171. package/reference/telemetry/common/resource.md +34 -0
  172. package/reference/telemetry/index.md +311 -0
  173. package/reference/telemetry/logging/index.md +197 -0
  174. package/reference/telemetry/logging/log-enrichment.md +72 -0
  175. package/reference/telemetry/logging/log-formatter.md +100 -0
  176. package/reference/telemetry/logging/log-record-processor.md +56 -0
  177. package/reference/telemetry/logging/log-record.md +44 -0
  178. package/reference/telemetry/logging/log-writer.md +64 -0
  179. package/reference/telemetry/logging/logger-provider.md +142 -0
  180. package/reference/telemetry/logging/logger.md +83 -0
  181. package/reference/telemetry/logging/severity.md +62 -0
  182. package/reference/telemetry/metrics/index.md +150 -0
  183. package/reference/telemetry/metrics/instruments.md +183 -0
  184. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  185. package/reference/telemetry/metrics/meter-provider.md +76 -0
  186. package/reference/telemetry/metrics/meter.md +98 -0
  187. package/reference/telemetry/metrics/metric-data.md +57 -0
  188. package/reference/telemetry/otel/custom-exporter.md +216 -0
  189. package/reference/telemetry/otel/index.md +212 -0
  190. package/reference/telemetry/tracing/index.md +155 -0
  191. package/reference/telemetry/tracing/sampler.md +89 -0
  192. package/reference/telemetry/tracing/span-builder.md +57 -0
  193. package/reference/telemetry/tracing/span-context.md +39 -0
  194. package/reference/telemetry/tracing/span-data.md +32 -0
  195. package/reference/telemetry/tracing/span-kind.md +55 -0
  196. package/reference/telemetry/tracing/span-processor.md +53 -0
  197. package/reference/telemetry/tracing/span-status.md +47 -0
  198. package/reference/telemetry/tracing/span.md +117 -0
  199. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  200. package/reference/telemetry/tracing/tracer.md +52 -0
  201. package/reference/typeid.md +5 -83
  202. package/sidebars.js +376 -43
  203. package/undocumented-report.md +528 -270
  204. package/reference/formats.md +0 -694
  205. package/reference/http-model.md +0 -1716
  206. package/reference/streams.md +0 -989
  207. package/ringbuffer.md +0 -249
  208. /package/reference/{json-differ.md → schema/built-in-codecs/json/json-differ.md} +0 -0
  209. /package/reference/{dynamic-value.md → schema/dynamic-value.md} +0 -0
  210. /package/reference/{lazy.md → schema/lazy.md} +0 -0
  211. /package/reference/{reflect.md → schema/reflect.md} +0 -0
  212. /package/reference/{registers.md → schema/registers.md} +0 -0
  213. /package/reference/{schema-error.md → schema/schema-error.md} +0 -0
  214. /package/reference/{syntax.md → schema/syntax.md} +0 -0
  215. /package/reference/{validation.md → schema/validation.md} +0 -0
@@ -0,0 +1,726 @@
1
+ ---
2
+ id: index
3
+ title: "Streams"
4
+ ---
5
+
6
+ `zio.blocks.streams` is a **pull-based** streaming library for **Scala 3** (and Scala 2.13) with synchronous and asynchronous readers, typed errors, resource safety, and primitive specialization. Streams are lazy descriptions -- nothing executes until a terminal operation is driven. Cross-platform terminals ending in `Async` return `Async[Either[E, Z]]`; the JVM also provides blocking terminals returning `Either[E, Z]`. The library has zero runtime dependencies beyond ZIO Blocks modules, and avoids boxing on primitive element types 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]`](./core/stream.md) | A lazy, pull-based sequence of elements that may fail with error `E` | `stream.via(pipe)` |
13
+ | [`Pipeline[-In, +Out]`](./core/pipeline.md) | A reusable, composable stream-to-stream transformation | `pipe.andThen(other)` |
14
+ | [`Sink[+E, -A, +Z]`](./core/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
+ **Dual execution.** Cross-platform `*Async` terminals drive either synchronous or asynchronous sources without blocking and require no ZIO runtime. On the JVM, plain terminals such as `run`, `runCollect`, and `head` are blocking compatibility twins.
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 JVM example. Streams are lazy descriptions — nothing executes until you call a terminal operation like `runCollect`. Use `runCollectAsync` for the cross-platform form.
29
+
30
+ Unless a section says otherwise, examples using plain terminals (`run`, `runCollect`, `head`, and their peers) are JVM-only shorthand. Replace them with the matching `*Async` terminal in shared JVM/Scala.js code.
31
+
32
+ ```scala
33
+ import zio.blocks.streams.*
34
+ import zio.blocks.chunk.Chunk
35
+
36
+ // Build a lazy stream description
37
+ val stream = Stream.range(1, 100)
38
+ .filter(_ % 2 == 0)
39
+ .map(_ * 3)
40
+
41
+ // Run it — nothing executes until here
42
+ val result = stream.take(5).runCollect
43
+ // Right(Chunk(6, 12, 18, 24, 30))
44
+ ```
45
+
46
+ ## Installation
47
+
48
+ Add the Streams module to your SBT build:
49
+
50
+ ```scala
51
+ libraryDependencies += "dev.zio" %% "zio-blocks-streams" % "0.0.55"
52
+ ```
53
+
54
+ For Scala.js (JavaScript/Node.js):
55
+
56
+ ```scala
57
+ libraryDependencies += "dev.zio" %%% "zio-blocks-streams" % "0.0.55"
58
+ ```
59
+
60
+ Supported Scala versions: 2.13.x and 3.x.
61
+
62
+ ## Why Streams?
63
+
64
+ Streaming libraries in the Scala ecosystem typically require an effect system. fs2 runs in a `cats.effect`-compatible `F[_]`, 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.
65
+
66
+ `zio.blocks.streams` fills that gap:
67
+
68
+ | Feature | ZB Streams | fs2 | Kyo | Ox | Pekko |
69
+ |---------------------------|-------------------------|----------------------|---------------|-------------------------|-----------------|
70
+ | Effect system required | No | Yes (cats-effect) | Yes (Kyo) | No (virtual threads) | Yes (Pekko) |
71
+ | Execution model | Sync/async, pull-based | Async, pull-based | Async, chunk | Synchronous, pull-based | Async, push |
72
+ | Typed errors | `Either[E, Z]` | Not verified here | Not verified here | Not verified here | Not verified here |
73
+ | Primitive specialization | Yes (zero boxing) | Not verified here | Not verified here | Not verified here | Not verified here |
74
+ | Stack-safe deep pipelines | Yes (trampolined) | Not verified here | Not verified here | Not verified here | Not verified here |
75
+ | Resource safety | Scope integration | Resource/bracket | Kyo resources | try/finally | Graph lifecycle |
76
+ | Dependencies | scope, chunk, combinators, ringbuffer, async | fs2-core | kyo-prelude, kyo-core | ox core | pekko-stream |
77
+
78
+ The provider columns name the artifacts and versions this repository pins for benchmarking — fs2 3.14.0, Pekko 1.7.0, Kyo 1.0.0-RC6, Ox 1.0.6 (`build.sbt`, `streams-benchmark/benchmark-manifest.json`). Nothing outside the ZB Streams column is measured or verified in this repository.
79
+
80
+ ## Benchmarks
81
+
82
+ The repository carries a JMH benchmark suite under `streams-benchmark`. Provider comparisons are
83
+ governed by the allowlist and provider versions recorded in
84
+ `streams-benchmark/benchmark-manifest.json`; results are only comparable within a single benchmark
85
+ class and contract, and are not aggregated into a ranking here. Re-run the benchmarks on your target
86
+ environment before drawing any performance conclusion.
87
+
88
+ If you are evaluating Scala 2 compatibility work, read the [Scala 2 compatibility design note](./execution-and-compatibility/scala-2-compatibility.md) before moving any `Stream` or `Sink` hot-path combinators behind version-specific seams.
89
+
90
+ ## Core Mental Model
91
+
92
+ 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.
93
+
94
+ ### Execution Flow
95
+
96
+ Operations on streams transform the pipeline and ultimately run it against a sink:
97
+
98
+ ```
99
+ ┌──────────────────────────────────┐
100
+ │ Stream[E, A] │
101
+ │ (lazy description) │
102
+ └──────────────────┬───────────────┘
103
+ │
104
+ .flatMap, .map, .filter, etc.
105
+ │
106
+ ┌──────────────────▼───────────────┐
107
+ │ Pipeline[-In, +Out] │
108
+ │ (stream → stream transformation) │
109
+ └──────────────────┬───────────────┘
110
+ │
111
+ .via(pipe)
112
+ │
113
+ ┌──────────────────▼───────────────┐
114
+ │ Sink[E, A, Z] │
115
+ │ (stream consumer → result Z) │
116
+ └──────────────────┬───────────────┘
117
+ │
118
+ .run(sink)
119
+ │
120
+ ┌──────────────────▼───────────────┐
121
+ │ Async[Either[E, Z]] │
122
+ │ (or blocking Either on JVM) │
123
+ └──────────────────────────────────┘
124
+ ```
125
+
126
+ The last box is where the flow forks. The same `Stream` description is materialized either as a
127
+ synchronous reader, drained on the calling thread by a blocking terminal such as `run` or
128
+ `runCollect`, or as an asynchronous reader, driven without blocking by the matching `*Async`
129
+ terminal. Which engine runs is decided by the source and operators the pipeline is built from, not
130
+ by the terminal you call. See [Async Execution](./execution-and-compatibility/async-execution.md) for how that classification
131
+ works.
132
+
133
+
134
+ ### 1) `Stream[E, A]` -- a Lazy Sequence
135
+
136
+ 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.
137
+
138
+ Nothing happens when you construct a stream or chain transformations. Execution only begins when you drive a terminal operation. Cross-platform terminals (`runAsync`, `runCollectAsync`, `headAsync`, `countAsync`, etc.) return `Async[Either[E, Z]]`; plain blocking terminals return `Either[E, Z]` on the JVM:
139
+
140
+ - `Left(e)` -- a typed stream error
141
+ - `Right(z)` -- the successful result
142
+
143
+ Untyped defects (unexpected exceptions) propagate as thrown exceptions, not as `Left` values.
144
+
145
+ ```scala
146
+ import zio.blocks.streams.*
147
+
148
+ // This does nothing -- it's just a description
149
+ val description: Stream[Nothing, Int] =
150
+ Stream.range(0, 1_000_000)
151
+ .filter(_ % 7 == 0)
152
+ .map(_ * 2)
153
+ .take(100)
154
+
155
+ // Only this line executes the pipeline
156
+ // val result = description.runCollect
157
+ ```
158
+
159
+ Streams render their pipeline structure as a human-readable string:
160
+
161
+ ```scala
162
+ val s = Stream.range(0, 100).map(_ + 1).filter(_ > 50).take(10)
163
+ println(s) // Stream.range(0, 100).map(...).filter(...).take(10)
164
+ ```
165
+
166
+ This makes debugging and logging straightforward -- you can see exactly what transformations a stream applies without running it.
167
+
168
+ ---
169
+
170
+ ### 2) `Sink[E, A, Z]` -- a Consumer
171
+
172
+ 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`:
173
+
174
+ ```scala
175
+ import zio.blocks.streams.*
176
+
177
+ val streamSinks = Stream.range(1, 101)
178
+
179
+ // Built-in sinks
180
+ val total = streamSinks.run(Sink.count)
181
+ val items = streamSinks.run(Sink.collectAll)
182
+ val sum = streamSinks.run(Sink.sumInt)
183
+ val first = streamSinks.run(Sink.head)
184
+ ```
185
+
186
+ Most sinks also have convenience methods directly on `Stream`:
187
+
188
+ ```scala
189
+ stream.count // Either[Nothing, Long]
190
+ stream.runCollect // Either[Nothing, Chunk[Int]]
191
+ stream.head // Either[Nothing, Option[Int]]
192
+ stream.last // Either[Nothing, Option[Int]]
193
+ ```
194
+
195
+ Sinks compose with `contramap` (pre-process input) and `map` (post-process result):
196
+
197
+ ```scala
198
+ val lengthSink: Sink[Nothing, String, Long] =
199
+ Sink.sumInt.contramap[Int, String](_.length)
200
+
201
+ val doubled: Sink[Nothing, Int, Long] =
202
+ Sink.sumInt.map(_ * 2)
203
+ ```
204
+
205
+ ---
206
+
207
+ ### 3) `Pipeline[In, Out]` -- Reusable Transformation
208
+
209
+ 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.
210
+
211
+ ```scala
212
+ // Define a reusable pipeline
213
+ val normalize: Pipeline[Int, Double] =
214
+ Pipeline.filter[Int](_ > 0)
215
+ .andThen(Pipeline.map[Int, Double](_.toDouble / 100.0))
216
+
217
+ // Apply to different streams
218
+ val result1 = Stream.range(-10, 10).via(normalize).runCollect
219
+ val result2 = Stream.fromIterable(List(42, -5, 100, 0)).via(normalize).runCollect
220
+ ```
221
+
222
+ Pipelines compose with `andThen`:
223
+
224
+ ```scala
225
+ val step1: Pipeline[String, Int] =
226
+ Pipeline.map[String, Int](_.length)
227
+
228
+ val step2: Pipeline[Int, Int] =
229
+ Pipeline.filter[Int](_ > 3)
230
+
231
+ val combined: Pipeline[String, Int] =
232
+ step1.andThen(step2)
233
+ ```
234
+
235
+ You can also apply a pipeline to a sink with `andThenSink` / `applyToSink`, which pre-processes the sink's input:
236
+
237
+ ```scala
238
+ val countLong: Sink[Nothing, String, Long] =
239
+ Pipeline.map[String, Int](_.length)
240
+ .andThenSink(Sink.sumInt)
241
+ ```
242
+
243
+ ## Synchronous and Asynchronous Execution
244
+
245
+ There is one `Stream` type. It serves both execution modes, and there is no mode type parameter, no
246
+ `AsyncStream`, and no annotation to write.
247
+
248
+ The type that decides is [`Reader`](./primitives/reader.md), not `Stream`. Materializing a stream yields either
249
+ a `Reader.SyncReader[A]`, whose pulls return values directly, or a `Reader.AsyncReader[A]`, whose
250
+ pulls return `Async` values. A pipeline that is synchronous end to end materializes as the former; a
251
+ single asynchronous source or operator anywhere in it makes the whole pipeline asynchronous.
252
+
253
+ The `*Async` terminals -- `runAsync`, `runCollectAsync`, `runDrainAsync`, `runFoldAsync`,
254
+ `countAsync`, `headAsync`, and their peers -- are the cross-platform API. They all return
255
+ `Async[Either[E, Z]]`: the typed error `E` stays inside the `Either`, while the outer `Async` fails
256
+ only on a defect. They drive a synchronous pipeline just as correctly as an asynchronous one, so
257
+ shared JVM/Scala.js code can use them unconditionally.
258
+
259
+ The blocking terminals -- `run`, `runCollect`, `runDrain`, `runFold`, `count`, `head`, and their
260
+ peers -- are **JVM-only** compatibility twins returning a bare `Either[E, Z]`. They do not exist on
261
+ Scala.js, and cross-compiled sources cannot call them.
262
+
263
+ - [Async Execution](./execution-and-compatibility/async-execution.md) -- the full execution model: classification, the `Reader`
264
+ union, the async source constructors, operators, and terminals.
265
+ - [Platform Differences](./execution-and-compatibility/platform-differences.md) -- the availability matrix of every member that
266
+ differs between the JVM and Scala.js.
267
+
268
+ ## Error Handling
269
+
270
+ Streams distinguish between two kinds of failures:
271
+
272
+ - **Typed errors** (`E`) — domain errors you expect and handle, returned as `Left` in the result. Use `catchAll`, `orElse`, or `mapError` to recover.
273
+ - **Defects** (`Throwable`) — unexpected exceptions from bugs or system failures. Use `catchDefect` to recover, or they propagate as thrown exceptions.
274
+
275
+ ```scala
276
+ val failing: Stream[String, Int] =
277
+ Stream.fromIterable(List(1, 2, 3)) ++ Stream.fail("oops") ++ Stream.fromIterable(List(4, 5))
278
+
279
+ val recovered = failing.catchAll(_ => Stream.fromIterable(List(99)))
280
+ recovered.runCollect // Right(Chunk(1, 2, 3, 99))
281
+ ```
282
+
283
+ ## Resource Management
284
+
285
+ Streams integrate with [`zio.blocks.scope.Scope`](../resource-management/scope.md) 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.
286
+
287
+ ```scala
288
+ import zio.blocks.streams.*
289
+
290
+ val managed = Stream.fromAcquireRelease(
291
+ acquire = scala.io.Source.fromFile("data.txt"),
292
+ release = _.close()
293
+ )(source => Stream.fromIterator(source.getLines()))
294
+
295
+ managed.take(10).runCollect
296
+ // File is closed in finally block regardless of outcome
297
+ ```
298
+
299
+ This eliminates the need for manual try/finally when working with resources — the stream handles it for you.
300
+
301
+ ## Primitive Specialization
302
+
303
+ ZB Streams carries the JVM representation of the element type through the whole pipeline, so a stream of primitives is not boxed at each stage boundary. Specialization is not limited to `Int`, `Long`, `Float`, and `Double`: there are **nine logical lanes** -- the eight primitive pull identities `Boolean`, `Byte`, `Short`, `Char`, `Int`, `Long`, `Float`, and `Double`, plus the reference fallback -- and the synchronous interpreter compacts them into **five storage lanes**: int-like (`Boolean`/`Byte`/`Short`/`Char`/`Int`), `Long`, `Float`, `Double`, and reference. Nine logical lanes therefore does not mean nine interpreter arrays; the operator tag selects the identity-specific reads over the shared storage.
304
+
305
+ [Zero-Boxing Streams](./execution-and-compatibility/zero-boxing.md) explains how a lane is chosen and what the specialization evidence is for.
306
+
307
+ ```scala
308
+ import zio.blocks.streams.*
309
+
310
+ // This entire pipeline runs with ZERO boxing of the Int elements.
311
+ // Every step uses specialized readInt/writeInt internally.
312
+ val sum: Either[Nothing, Long] =
313
+ Stream.range(0, 1_000_000) // Int-specialized source
314
+ .filter(_ % 2 == 0) // Int-specialized filter
315
+ .map(_ * 3) // Int->Int specialized map
316
+ .runFold(0L)(_ + _) // Long-specialized accumulator
317
+ ```
318
+
319
+ This matters most for numeric workloads — data processing, statistics, encoding/decoding — where millions of elements flow through multi-stage pipelines.
320
+
321
+ ## Practical Guidance
322
+
323
+ - **Start with `Stream` constructors and terminal operations.** You can get very far with `Stream.range`, `Stream.fromIterable`, `.map`, `.filter`, and `.runCollect`.
324
+ - **Use `Either` pattern matching** to handle the result: `Right(value)` for success, `Left(error)` for typed failures.
325
+ - **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.
326
+ - **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.
327
+ - **Use `Pipeline`** when you have a transformation you want to reuse across multiple streams or apply to sinks.
328
+ - **Use `&&` for zipping** instead of manual zip calls. Tuples flatten automatically: `a && b && c` produces `(A, B, C)` not `((A, B), C)`.
329
+ - **Leverage primitive specialization** for numeric workloads. Streams of any primitive element type avoid boxing automatically on the synchronous path; use `Sink.sumInt`, `runFold(0)(_ + _)`, etc. for allocation-free folds.
330
+ - **Use `scan` for running accumulators**, `grouped` for batching, and `sliding` for windowed computations.
331
+ - **Use `render`/`toString`** to inspect pipeline structure during debugging — it shows each transformation stage without executing the stream.
332
+ - **Use `Sink.create`** (JVM only) as an escape hatch when none of the built-in sinks fit; on Scala.js and in cross-compiled code use `Sink.createAsync` or `Sink.createBoth`.
333
+ - **`suspend`** is your friend for recursive or self-referential stream definitions, preventing stack overflow during construction.
334
+ - **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.
335
+
336
+ ## Usage Examples
337
+
338
+ 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.
339
+
340
+ ### Creating Streams
341
+
342
+ Here are the most common ways to construct a stream. Choose the constructor that best fits your data source:
343
+
344
+
345
+ ```scala
346
+ import zio.blocks.streams.*
347
+ import zio.blocks.chunk.Chunk
348
+
349
+ // From explicit elements
350
+ Stream.fromIterable(List(1, 2, 3)) // Stream[Nothing, Int]
351
+ Stream.fromIterable(List("a", "b", "c")) // Stream[Nothing, String]
352
+
353
+ // From collections
354
+ Stream.fromChunk(Chunk(1, 2, 3)) // Stream[Nothing, Int]
355
+ Stream.fromIterable(List("x", "y", "z")) // Stream[Nothing, String]
356
+ Stream.fromIterator(Iterator.from(1)) // Stream[Nothing, Int] (lazy)
357
+
358
+ // Ranges
359
+ Stream.range(0, 100) // 0 to 99
360
+ Stream.fromRange(1 to 50) // 1 to 50
361
+
362
+ // Single values (primitive-specialized)
363
+ Stream.succeed(42) // Stream[Nothing, Int]
364
+ Stream.succeed(3.14) // Stream[Nothing, Double]
365
+ Stream.succeed("hello") // Stream[Nothing, String]
366
+
367
+ // Special streams
368
+ Stream.empty // Stream[Nothing, Nothing]
369
+ Stream.fail("error") // Stream[String, Nothing]
370
+ // Stream.die(new Exception("defect")) // throws on evaluation
371
+
372
+ // Generators
373
+ Stream.repeat(1) // infinite stream of 1s
374
+ Stream.unfold(0)(n => // 0, 1, 2, ..., 9
375
+ if n < 10 then Some((n, n + 1)) else None
376
+ )
377
+
378
+ // Side-effects
379
+ Stream.eval(println("hello")) // prints, emits nothing
380
+ Stream.attempt(someFallibleCall()) // captures exceptions as typed errors
381
+ Stream.attemptEval(riskyEffect()) // same, for Unit-returning effects
382
+
383
+ // Deferred construction (useful for recursion)
384
+ Stream.suspend(expensiveStreamBuilder())
385
+
386
+ // I/O sources (auto-closing) - JVM only
387
+ Stream.fromInputStream(inputStream) // Stream[IOException, Byte] (auto-closes)
388
+ Stream.fromJavaReader(javaReader) // Stream[IOException, Char] (auto-closes)
389
+
390
+ // I/O sources (borrowing -- caller manages lifetime) - JVM only
391
+ Stream.fromInputStreamUnmanaged(inputStream) // Stream[IOException, Byte] (does NOT close)
392
+ Stream.fromJavaReaderUnmanaged(javaReader) // Stream[IOException, Char] (does NOT close)
393
+ ```
394
+
395
+ ---
396
+
397
+ ### Transforming Streams
398
+
399
+ 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](./core/stream.md) page for comprehensive examples of all transformation methods including `map`, `filter`, `flatMap`, `collect`, `scan`, `mapAccum`, `distinct`, `intersperse`, and more.
400
+
401
+ ---
402
+
403
+ ### Zipping Streams with `&&`
404
+
405
+ The `&&` operator zips two streams element-by-element into tuples. The resulting stream ends when either input is exhausted.
406
+
407
+ ```scala
408
+ import zio.blocks.streams.*
409
+
410
+ val names: Stream[Nothing, String] = Stream.fromIterable(List("Alice", "Bob", "Charlie"))
411
+ val ages: Stream[Nothing, Int] = Stream.fromIterable(List(30, 25, 35))
412
+ val ids: Stream[Nothing, Long] = Stream.fromIterable(List(1L, 2L, 3L))
413
+
414
+ // Two-way zip
415
+ val pairs = names && ages
416
+ pairs.runCollect // Right(Chunk(("Alice", 30), ("Bob", 25), ("Charlie", 35)))
417
+
418
+ // Three-way zip -- tuples flatten automatically
419
+ val triples = names && ages && ids
420
+ triples.runCollect // Right(Chunk(("Alice", 30, 1L), ("Bob", 25, 2L), ("Charlie", 35, 3L)))
421
+ ```
422
+
423
+ When the error types differ, they widen through `Concat` — to a union `E1 | E2` on Scala 3, and to a meaningful least upper bound (or `Either[E1, E2]` for disjoint types) on Scala 2.13:
424
+
425
+ ```scala
426
+ import zio.blocks.streams.*
427
+
428
+ sealed trait MyError
429
+ val s1: Stream[MyError, Int] = Stream.fromIterable(List(1, 2, 3))
430
+ sealed trait OtherError
431
+ val s2: Stream[OtherError, Int] = Stream.fromIterable(List(4, 5, 6))
432
+ // val zipped = s1 && s2
433
+ ```
434
+
435
+ ---
436
+
437
+ ### Primitive Specialization
438
+
439
+ All nine logical lanes are specialized, not only `Int`, `Long`, `Float`, and `Double`. Every intermediate step uses the identity-specific read (`readInt`, `readByte`, `readChar`, and so on), so no `java.lang.Integer` wrappers are allocated between stages.
440
+
441
+ ```scala
442
+ // This entire pipeline runs with ZERO boxing of the Int elements.
443
+ // Every step uses specialized readInt/writeInt internally.
444
+ val sum: Either[Nothing, Long] =
445
+ Stream.range(0, 1_000_000) // Int-specialized source
446
+ .filter(_ % 2 == 0) // Int-specialized filter
447
+ .map(_ * 3) // Int->Int specialized map
448
+ .runFold(0L)(_ + _) // Long-specialized accumulator
449
+ ```
450
+
451
+ This matters most for numeric workloads -- data processing, statistics, encoding/decoding -- where millions of elements flow through multi-stage pipelines.
452
+
453
+ ---
454
+
455
+ ### Consuming Streams
456
+
457
+ 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`:
458
+
459
+ ```scala
460
+ val s = Stream.range(1, 11) // 1 to 10
461
+
462
+ // Collect all elements
463
+ s.runCollect // Right(Chunk(1, 2, 3, ..., 10))
464
+
465
+ // Discard all elements (run for side-effects only)
466
+ s.tapEach(println).runDrain
467
+
468
+ // Fold
469
+ s.runFold(0)(_ + _) // Right(55) (Int accumulator)
470
+ s.runFold(0L)(_ + _) // Right(55L) (Long accumulator)
471
+ s.runFold(0.0)(_ + _) // Right(55.0) (Double accumulator)
472
+
473
+ // Foreach
474
+ s.runForeach(n => println(n))
475
+ s.foreach(n => println(n)) // alias
476
+
477
+ // Aggregates
478
+ s.count // Right(10L)
479
+ s.head // Right(Some(1))
480
+ s.last // Right(Some(10))
481
+ s.exists(_ > 5) // Right(true)
482
+ s.forall(_ > 0) // Right(true)
483
+ s.find(_ > 7) // Right(Some(8))
484
+
485
+ // Run with an explicit Sink
486
+ s.run(Sink.sumInt) // Right(55L)
487
+ s.run(Sink.take(3)) // Right(Chunk(1, 2, 3))
488
+ ```
489
+
490
+ ---
491
+
492
+ ### Async Execution
493
+
494
+ `runCollectAsync` is the cross-platform twin of `runCollect`. It returns `Async[Either[E, Chunk[A]]]`
495
+ rather than `Either[E, Chunk[A]]`, so it never blocks and compiles on both the JVM and Scala.js:
496
+
497
+ ```scala
498
+ import zio.blocks.streams._
499
+ import zio.blocks.chunk.Chunk
500
+ import zio.blocks.async._
501
+
502
+ val evens: Stream[Nothing, Int] =
503
+ Stream.range(1, 100).filter(_ % 2 == 0).map(_ * 3)
504
+
505
+ // Still a description -- nothing has run.
506
+ val pending: Async[Either[Nothing, Chunk[Int]]] = evens.take(5).runCollectAsync
507
+
508
+ // Stay in Async: map the result rather than extracting it.
509
+ val described: Async[String] = pending.map {
510
+ case Right(values) => s"collected ${values.length} elements"
511
+ case Left(error) => s"failed: $error"
512
+ }
513
+ ```
514
+
515
+ Something has to drive the `Async` at the edge of the world. On the JVM that is `.block`, which
516
+ belongs in `main` or a test and nowhere else:
517
+
518
+ ```scala
519
+ import zio.blocks.streams._
520
+ import zio.blocks.chunk.Chunk
521
+ import zio.blocks.async._
522
+
523
+ // JVM only: Async#block throws on Scala.js.
524
+ val result: Either[Nothing, Chunk[Int]] =
525
+ Stream.range(1, 100).filter(_ % 2 == 0).take(5).runCollectAsync.block
526
+ // Right(Chunk(2, 4, 6, 8, 10))
527
+ ```
528
+
529
+ Scala.js code keeps the `Async` and hands it to the host runtime instead. See
530
+ [Async Execution](./execution-and-compatibility/async-execution.md) for the full terminal family and
531
+ [Platform Differences](./execution-and-compatibility/platform-differences.md) for what is available where.
532
+
533
+ ---
534
+
535
+ ### Error Handling Patterns
536
+
537
+ 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:
538
+
539
+ ```scala
540
+ // Typed error: appears in Either
541
+ val result = Stream.fail("not found").runCollect
542
+ // result: Left("not found")
543
+
544
+ // Recover and continue
545
+ val safe =
546
+ Stream.fromIterable(List(1, 2)) ++ Stream.fail("oops") ++ Stream.fromIterable(List(3))
547
+ val recovered = safe.catchAll(_ => Stream.fromIterable(List(99))).runCollect
548
+ // Right(Chunk(1, 2, 99))
549
+
550
+ // Transform error type by catching and converting
551
+ val inputError: Stream[String, Int] = Stream.fail("bad input")
552
+ val transformed = inputError.catchAll(msg => Stream.fail(new IllegalArgumentException(msg)))
553
+
554
+ // Fallback stream
555
+ val primary: Stream[String, Int] = Stream.fail("down")
556
+ val backup: Stream[String, Int] = Stream.fromIterable(List(1, 2, 3))
557
+ val result2 = (primary || backup).runCollect
558
+ // Right(Chunk(1, 2, 3))
559
+
560
+ // Catch defects (unexpected exceptions)
561
+ val risky: Stream[Nothing, Int] =
562
+ Stream.fromIterable(List(1, 2, 3)).map { n =>
563
+ if n == 2 then throw new ArithmeticException("boom")
564
+ else n
565
+ }
566
+
567
+ val handled = risky.catchDefect {
568
+ case _: ArithmeticException => Stream.fromIterable(List(0))
569
+ }.runCollect
570
+ // Right(Chunk(1, 0))
571
+ ```
572
+
573
+ ---
574
+
575
+ ### Resource Safety Patterns
576
+
577
+ When working with files, network connections, or other resources, use the resource-safe constructors to guarantee cleanup. Here are the most common patterns:
578
+
579
+ ```scala
580
+ import zio.blocks.streams.*
581
+ import zio.blocks.scope.*
582
+
583
+ // Bracket pattern: acquire/use/release
584
+ def fileLines(path: String): Stream[Nothing, String] =
585
+ Stream.fromAcquireRelease(
586
+ acquire = scala.io.Source.fromFile(path),
587
+ release = _.close()
588
+ ) { source =>
589
+ Stream.fromIterable(source.getLines().toList)
590
+ }
591
+
592
+ // Compose resource-safe streams -- both resources are released
593
+ val merged =
594
+ fileLines("input1.txt") ++ fileLines("input2.txt")
595
+
596
+ // Only reads 10 lines; both files are still closed properly
597
+ merged.take(10).runCollect
598
+
599
+ // ensuring: attach a finalizer
600
+ var cleaned = false
601
+ Stream.range(1, 6)
602
+ .ensuring { cleaned = true }
603
+ .take(2)
604
+ .runDrain
605
+ // cleaned == true, even though only 2 of 5 elements were consumed
606
+
607
+ // defer: register cleanup that runs on stream close
608
+ val withDefer =
609
+ Stream.defer(println("releasing lock")) ++
610
+ Stream.range(1, 100)
611
+ ```
612
+
613
+ ---
614
+
615
+ ### NIO Integration (JVM Only)
616
+
617
+ On the JVM, `NioStreams` and `NioSinks` provide zero-copy integration with `java.nio` buffers and channels.
618
+
619
+ #### `NioStreams` -- Creating Streams From NIO Sources
620
+
621
+ ```scala
622
+ import zio.blocks.streams.*
623
+ import java.nio.ByteBuffer
624
+ import java.nio.channels.FileChannel
625
+ import java.nio.file.{Paths, StandardOpenOption}
626
+
627
+ // From a ByteBuffer
628
+ val buf = ByteBuffer.wrap(Array[Byte](1, 2, 3, 4, 5))
629
+ NioStreams.fromByteBuffer(buf).runCollect
630
+ // Right(Chunk(1, 2, 3, 4, 5))
631
+
632
+ // Typed buffer views (zero-boxing)
633
+ val intBuf = ByteBuffer.allocate(16).putInt(1).putInt(2).putInt(3).putInt(4).flip()
634
+ NioStreams.fromByteBufferInt(intBuf).runCollect
635
+ // Right(Chunk(1, 2, 3, 4))
636
+
637
+ // Similarly: fromByteBufferLong, fromByteBufferFloat, fromByteBufferDouble
638
+
639
+ // From a ReadableByteChannel (auto-closing)
640
+ val ch = FileChannel.open(Paths.get("data.bin"), StandardOpenOption.READ)
641
+ val bytes = NioStreams.fromChannel(ch, bufSize = 4096).runCollect
642
+ // ch is closed automatically when the stream completes
643
+
644
+ // From a ReadableByteChannel (borrowing -- caller manages lifetime)
645
+ val ch2 = FileChannel.open(Paths.get("data.bin"), StandardOpenOption.READ)
646
+ val bytes2 = NioStreams.fromChannelUnmanaged(ch2, bufSize = 4096).runCollect
647
+ ch2.close() // caller is responsible for closing
648
+ ```
649
+
650
+ #### `NioSinks` -- Writing to NIO Targets
651
+
652
+ ```scala
653
+ import zio.blocks.streams.*
654
+ import zio.blocks.chunk.Chunk
655
+ import java.nio.ByteBuffer
656
+ import java.nio.channels.FileChannel
657
+ import java.nio.file.{Files, StandardOpenOption}
658
+
659
+ // Write to a ByteBuffer using a typed sink (Int values, zero-boxing)
660
+ val outBuf = ByteBuffer.allocate(1024)
661
+ Stream.range(1, 5).run(NioSinks.fromByteBufferInt(outBuf))
662
+
663
+ // Write to a WritableByteChannel using a stream of bytes
664
+ val tempPath = Files.createTempFile("zio-blocks-streams-", ".bin")
665
+ val outCh = FileChannel.open(
666
+ tempPath,
667
+ StandardOpenOption.WRITE,
668
+ StandardOpenOption.TRUNCATE_EXISTING
669
+ )
670
+ val bytes = Chunk.fromIterable(List[Byte](1, 2, 3, 4, 5))
671
+ try Stream.fromChunk(bytes).run(NioSinks.fromChannel(outCh))
672
+ finally {
673
+ outCh.close()
674
+ Files.deleteIfExists(tempPath)
675
+ }
676
+ ```
677
+
678
+ ---
679
+
680
+ ### Pipeline Composition
681
+
682
+ Pipelines are composable transformations that can be reused across different streams. Build complex transformations by chaining pipelines together with `andThen`:
683
+
684
+ ```scala
685
+ import zio.blocks.streams.*
686
+
687
+ // Build reusable transformation steps
688
+ val parseInts: Pipeline[String, Int] =
689
+ Pipeline.collect[String, Int] {
690
+ case s if s.matches("-?\\d+") => s.toInt
691
+ }
692
+
693
+ val positiveOnly: Pipeline[Int, Int] =
694
+ Pipeline.filter[Int](_ > 0)
695
+
696
+ val doubled: Pipeline[Int, Int] =
697
+ Pipeline.map[Int, Int](_ * 2)
698
+
699
+ // Compose into a single pipeline
700
+ val fullPipeline: Pipeline[String, Int] =
701
+ parseInts
702
+ .andThen(positiveOnly)
703
+ .andThen(doubled)
704
+
705
+ // Apply to any stream of strings
706
+ Stream.fromIterable(List("10", "abc", "-3", "7", "0", "25"))
707
+ .via(fullPipeline)
708
+ .runCollect
709
+ // Right(Chunk(20, 14, 50))
710
+
711
+ // Apply to a sink (pre-process the sink's input)
712
+ val sumPositiveDoubled: Sink[Nothing, String, Long] =
713
+ fullPipeline.andThenSink(Sink.sumInt)
714
+
715
+ Stream.fromIterable(List("10", "abc", "-3", "7", "0", "25"))
716
+ .run(sumPositiveDoubled)
717
+ // Right(84L)
718
+ ```
719
+
720
+ ## See Also
721
+
722
+ - [Async Execution](./execution-and-compatibility/async-execution.md) -- the synchronous/asynchronous execution model, the `Reader` union, and the `*Async` terminal family
723
+ - [Platform Differences](./execution-and-compatibility/platform-differences.md) -- which members exist on the JVM, on Scala.js, and on both
724
+ - [Zero-Boxing Streams](./execution-and-compatibility/zero-boxing.md) -- the primitive lanes and how one is chosen
725
+ - [Async](../async.md) -- the `Async` effect type the cross-platform terminals return
726
+ - [Mux](../mux.md) -- coordinating many keyed streams over one shared transport