@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,718 @@
1
+ ---
2
+ id: pipeline
3
+ title: "Pipeline"
4
+ ---
5
+
6
+ `Pipeline[-In, +Out]` is a **reusable, composable stream transformation** that converts elements of type `In` into elements of type `Out`. Pipelines are first-class values: you can define them once, compose them with `andThen`, and apply them to any [Stream](./stream.md) via `stream.via(pipe)` or to any `Sink` via `pipe.andThenSink(sink)`.
7
+
8
+ `Pipeline`:
9
+ - Is contravariant in `In` and covariant in `Out` (like a function `In => Out`)
10
+ - Can be applied to a **Stream** (transforming the output) or a **Sink** (pre-processing the input)
11
+ - Participates in JVM primitive specialization to avoid boxing
12
+
13
+ Here is the structural shape of the `Pipeline` type:
14
+
15
+ ```scala
16
+ abstract class Pipeline[-In, +Out] {
17
+ def andThen[C](that: Pipeline[Out, C]): Pipeline[In, C]
18
+ def applyToStream[E](stream: Stream[E, In]): Stream[E, Out]
19
+ def applyToSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
20
+ }
21
+ ```
22
+
23
+ ## Overview
24
+
25
+ Pipelines solve the problem of reusing stream transformations across different streams and sinks. Without pipelines, you repeat filtering and mapping logic for every stream. With pipelines, you define transformations once as first-class values, compose them freely, and apply them anywhere.
26
+
27
+ ### The Problem
28
+
29
+ When you build stream processing logic, you often write chains like:
30
+
31
+ ```scala
32
+ stream
33
+ .filter(_ > 0)
34
+ .map(_ * 2)
35
+ .take(100)
36
+ ```
37
+
38
+ This works, but the transformation is tied to a specific stream. If you want to apply the same logic to a different stream, or to a sink instead, you have to repeat yourself. You cannot pass the chain around as a value, store it in a variable, or compose it with other transformations.
39
+
40
+ ### The Solution
41
+
42
+ `Pipeline[-In, +Out]` lifts stream transformations into first-class values. You define a pipeline once, compose it with other pipelines using `andThen`, and apply it wherever you need:
43
+
44
+ ```scala
45
+ import zio.blocks.streams.*
46
+
47
+ // Define once
48
+ val normalize: Pipeline[Int, Int] =
49
+ Pipeline.filter[Int](_ > 0)
50
+ .andThen(Pipeline.map[Int, Int](_ * 2))
51
+ .andThen(Pipeline.take(100))
52
+
53
+ // Apply to any stream
54
+ val stream1 = Stream(1, 2, 3, 4, 5)
55
+ val stream2 = Stream(10, 20, 30, 40, 50)
56
+ val stream3 = Stream(-5, 3, 7, 2, 8, 1, 9)
57
+
58
+ val result1 = stream1.via(normalize).runCollect
59
+ val result2 = stream2.via(normalize).runCollect
60
+
61
+ // Apply to a sink (pre-process input before the sink sees it)
62
+ val normalizedSink = normalize.andThenSink(Sink.collectAll[Int])
63
+ val result3 = stream3.run(normalizedSink)
64
+ ```
65
+
66
+ `Pipeline` forms a **category** in the mathematical sense:
67
+
68
+ | Law | Statement |
69
+ |----------------|------------------------------------------------------|
70
+ | Left identity | `Pipeline.identity andThen p == p` |
71
+ | Right identity | `p andThen Pipeline.identity == p` |
72
+ | Associativity | `(p andThen q) andThen r == p andThen (q andThen r)` |
73
+
74
+ These laws guarantee that pipelines compose predictably, regardless of how you parenthesize.
75
+
76
+ ### Architecture
77
+
78
+ `Pipeline` sits between `Stream` and `Sink`, mediating how elements flow:
79
+
80
+ ```
81
+ Applying to a Stream (via):
82
+ ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
83
+ │ Stream[E, In]│ ──→ │ Pipeline[In, Out]│ ──→ │Stream[E, Out]│
84
+ └──────────────┘ └──────────────────┘ └──────────────┘
85
+
86
+ Applying to a Sink (andThenSink):
87
+ ┌──────────────────┐ ┌────────────────┐ ┌──────────────┐
88
+ │ Pipeline[In, Out]│ ──→ │ Sink[E, Out, Z]│ ──→ │Sink[E, In, Z]│
89
+ └──────────────────┘ └────────────────┘ └──────────────┘
90
+
91
+ Composing two Pipelines (andThen):
92
+ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐
93
+ │ Pipeline[A, B] │ ──→ │ Pipeline[B, C] │ ──→ │ Pipeline[A, C] │
94
+ └──────────────────┘ └──────────────────┘ └─────────────────┘
95
+ ```
96
+
97
+ ## Construction
98
+
99
+ Pipelines are built using factory methods on the `Pipeline` companion object. Each factory creates a pipeline that performs a specific transformation: mapping elements, filtering, collecting, or controlling flow. All factories support JVM primitive specialization through implicit `JvmType.Infer` parameters.
100
+
101
+ ### `Pipeline.map[A, B]` — Transform Each Element
102
+
103
+ Applies a function to every element, producing a new element type. Here is the signature:
104
+
105
+ ```scala
106
+ object Pipeline {
107
+ def map[A, B](f: A => B)(implicit jtA: JvmType.Infer[A], jtB: JvmType.Infer[B]): Pipeline[A, B]
108
+ }
109
+ ```
110
+
111
+ This is the most common pipeline constructor:
112
+
113
+ ```scala
114
+ import zio.blocks.streams.*
115
+
116
+ val doubler = Pipeline.map[Int, Int](_ * 2)
117
+ // doubler: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$MapPipeline@5751527e
118
+ val toStr = Pipeline.map[Int, String](_.toString)
119
+ // toStr: Pipeline[Int, String] = zio.blocks.streams.Pipeline$MapPipeline@21969325
120
+
121
+ val result = Stream(1, 2, 3).via(doubler).runCollect
122
+ // result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(2, 4, 6))
123
+ ```
124
+
125
+ ### `Pipeline.filter[A]` — Keep Matching Elements
126
+
127
+ Keeps only elements that satisfy a predicate. Here is the signature:
128
+
129
+ ```scala
130
+ object Pipeline {
131
+ def filter[A](pred: A => Boolean)(implicit jtA: JvmType.Infer[A]): Pipeline[A, A]
132
+ }
133
+ ```
134
+
135
+ Note that the output type is the same as the input type — filtering does not change the element type:
136
+
137
+ ```scala
138
+ import zio.blocks.streams.*
139
+
140
+ val positives = Pipeline.filter[Int](_ > 0)
141
+ // positives: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$FilterPipeline@2148b90d
142
+
143
+ val result = Stream(-2, -1, 0, 1, 2).via(positives).runCollect
144
+ // result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(1, 2))
145
+ ```
146
+
147
+ ### `Pipeline.collect[A, B]` — Partial Function Transformation
148
+
149
+ Applies a partial function: only elements for which the function is defined pass through, and they are transformed to the output type. This combines filtering and mapping in one step. Here is the signature:
150
+
151
+ ```scala
152
+ object Pipeline {
153
+ def collect[A, B](pf: PartialFunction[A, B])(implicit jtA: JvmType.Infer[A], jtB: JvmType.Infer[B]): Pipeline[A, B]
154
+ }
155
+ ```
156
+
157
+ Use `collect` when you need to filter and transform simultaneously:
158
+
159
+ ```scala
160
+ import zio.blocks.streams.*
161
+
162
+ val extractInts = Pipeline.collect[Any, Int] { case n: Int => n }
163
+ // extractInts: Pipeline[Any, Int] = zio.blocks.streams.Pipeline$CollectPipeline@63a54682
164
+
165
+ val result = Stream(1, "a", 2, "b", 3).via(extractInts).runCollect
166
+ // result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(1, 2, 3))
167
+ ```
168
+
169
+ ### `Pipeline.take[A]` — First N Elements
170
+
171
+ Passes through at most the first `n` elements, then stops. Here is the signature:
172
+
173
+ ```scala
174
+ object Pipeline {
175
+ def take[A](n: Long): Pipeline[A, A]
176
+ }
177
+ ```
178
+
179
+ This naturally short-circuits — upstream stops producing once `n` elements have passed:
180
+
181
+ ```scala
182
+ import zio.blocks.streams.*
183
+
184
+ val firstFive = Pipeline.take[Int](5)
185
+ // firstFive: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$TakePipeline@53108fba
186
+
187
+ val result = Stream.range(0, 1000).via(firstFive).runCollect
188
+ // result: Either[Nothing, Chunk[Int]] = Right(IndexedSeq(0, 1, 2, 3, 4))
189
+ ```
190
+
191
+ ### `Pipeline.drop[A]` — Skip First N Elements
192
+
193
+ Skips the first `n` elements, then passes through the rest. Here is the signature:
194
+
195
+ ```scala
196
+ object Pipeline {
197
+ def drop[A](n: Long): Pipeline[A, A]
198
+ }
199
+ ```
200
+
201
+ Use `drop` to skip headers, metadata, or warm-up elements:
202
+
203
+ ```scala
204
+ import zio.blocks.streams.*
205
+
206
+ val skipHeader = Pipeline.drop[String](1)
207
+ // skipHeader: Pipeline[String, String] = zio.blocks.streams.Pipeline$DropPipeline@5e27a41e
208
+
209
+ val result = Stream("header", "row1", "row2").via(skipHeader).runCollect
210
+ // result: Either[Nothing, Chunk[String]] = Right(IndexedSeq("row1", "row2"))
211
+ ```
212
+
213
+ ### `Pipeline.identity[A]` — Pass-Through
214
+
215
+ The identity pipeline that passes all elements through unchanged. This is the neutral element for `andThen` composition. Here is the signature:
216
+
217
+ ```scala
218
+ object Pipeline {
219
+ def identity[A](implicit jtA: JvmType.Infer[A]): Pipeline[A, A]
220
+ }
221
+ ```
222
+
223
+ You rarely construct `identity` explicitly, but it is important as a base case in generic pipeline-building code:
224
+
225
+ ```scala
226
+ import zio.blocks.streams.*
227
+
228
+ val noOp = Pipeline.identity[Int]
229
+ // noOp: Pipeline[Int, Int] = zio.blocks.streams.Pipeline$MapPipeline@52ba998
230
+
231
+ // These are equivalent:
232
+ // stream.via(noOp) == stream
233
+ // noOp.andThen(p) == p
234
+ // p.andThen(noOp) == p
235
+ ```
236
+
237
+ ## Composing Pipelines
238
+
239
+ Pipelines compose into larger, more complex transformations using `andThen`. Because `Pipeline` forms a mathematical category, composition is associative and respects identity, so you can build pipelines incrementally or conditionally without worrying about how you parenthesize or combine them.
240
+
241
+ ### `Pipeline#andThen[C]` — Sequential Composition
242
+
243
+ Composes two pipelines into one, applying `this` first and `that` second. Here is the signature:
244
+
245
+ ```scala
246
+ trait Pipeline[-In, +Out] {
247
+ def andThen[C](that: Pipeline[Out, C]): Pipeline[In, C]
248
+ }
249
+ ```
250
+
251
+ `andThen` is the key operation that makes pipelines composable. Because `Pipeline` forms a category, composition is associative — you can group `andThen` calls however you like and get the same result:
252
+
253
+ ```scala
254
+ import zio.blocks.streams.*
255
+
256
+ // Individual steps
257
+ val filterPositive = Pipeline.filter[Int](_ > 0)
258
+ val double = Pipeline.map[Int, Int](_ * 2)
259
+ val takeFirst10 = Pipeline.take[Int](10)
260
+
261
+ // Compose into a single reusable pipeline
262
+ val normalize = filterPositive
263
+ .andThen(double)
264
+ .andThen(takeFirst10)
265
+
266
+ // Apply to any stream
267
+ val result = Stream(-5, 3, -1, 7, 2, 0, 9, 4, 8, 6, 1, 10)
268
+ .via(normalize)
269
+ .runCollect
270
+ ```
271
+
272
+ ### Building Pipelines Conditionally
273
+
274
+ Because pipelines are values, you can build them dynamically:
275
+
276
+ ```scala
277
+ import zio.blocks.streams.*
278
+
279
+ def buildPipeline(limit: Option[Int], onlyPositive: Boolean): Pipeline[Int, Int] = {
280
+ val base = if (onlyPositive) Pipeline.identity[Int].andThen(Pipeline.filter(_ > 0)) else Pipeline.identity[Int]
281
+ limit.fold(base)(n => base.andThen(Pipeline.take(n.toLong)))
282
+ }
283
+ ```
284
+
285
+ ## Applying to a Stream
286
+
287
+ Apply a pipeline to a stream using `via` to transform its output elements. This is the most direct way to use a pipeline: define it once and apply it to multiple streams without repeating the transformation logic.
288
+
289
+ ### `Stream#via[B]` — Apply a Pipeline to a Stream
290
+
291
+ The primary way to use a pipeline is through `Stream.via`. Here is the signature:
292
+
293
+ ```scala
294
+ trait Stream[+E, +A] {
295
+ def via[B](pipe: Pipeline[A, B]): Stream[E, B]
296
+ }
297
+ ```
298
+
299
+ Under the hood, `via` calls `pipe.applyToStream(this)`. Each pipeline type delegates to a specific `Stream` node — for example, `Pipeline.map` creates a `Stream.Mapped`, and `Pipeline.filter` creates a `Stream.Filtered`.
300
+
301
+ The key advantage of `via` over inline methods is **reuse**: define the pipeline once and apply it to multiple streams:
302
+
303
+ ```scala
304
+ import zio.blocks.streams.*
305
+
306
+ // A reusable cleaning pipeline for sensor data
307
+ val cleanSensorData: Pipeline[Double, Double] =
308
+ Pipeline.filter[Double](d => !d.isNaN && !d.isInfinite)
309
+ .andThen(Pipeline.filter(d => d >= -100.0 && d <= 100.0))
310
+ // cleanSensorData: Pipeline[Double, Double] = zio.blocks.streams.Pipeline$Composed@1eea4c07
311
+
312
+ // Apply to different sensor streams
313
+ val sensorStream1 = Stream(45.5, 67.2, Double.NaN, 23.1)
314
+ // sensorStream1: Stream[Nothing, Double] = Stream(45.5, 67.2, NaN, 23.1)
315
+ val sensorStream2 = Stream(89.9, -200.0, 12.5, 55.0)
316
+ // sensorStream2: Stream[Nothing, Double] = Stream(89.9, -200.0, 12.5, 55.0)
317
+
318
+ val sensor1Result = sensorStream1.via(cleanSensorData).runCollect
319
+ // sensor1Result: Either[Nothing, Chunk[Double]] = Right(
320
+ // IndexedSeq(45.5, 67.2, 23.1)
321
+ // )
322
+ val sensor2Result = sensorStream2.via(cleanSensorData).runCollect
323
+ // sensor2Result: Either[Nothing, Chunk[Double]] = Right(
324
+ // IndexedSeq(89.9, 12.5, 55.0)
325
+ // )
326
+ ```
327
+
328
+ ### `Pipeline#applyToStream[E]` — Direct Application
329
+
330
+ You can also call `applyToStream` directly. This is equivalent to `via` but reads left-to-right from the pipeline's perspective. Here is the signature:
331
+
332
+ ```scala
333
+ trait Pipeline[-In, +Out] {
334
+ def applyToStream[E](stream: Stream[E, In]): Stream[E, Out]
335
+ }
336
+ ```
337
+
338
+ `stream.via(pipe)` and `pipe.applyToStream(stream)` are identical in behavior. Prefer `via` for readability in stream chains.
339
+
340
+ ## Applying to a Sink
341
+
342
+ Apply a pipeline to a sink using `andThenSink` to pre-process the sink's input elements. This is the dual of `via`: instead of transforming a stream's output, you transform what the sink receives before it processes it.
343
+
344
+ ### `Pipeline#andThenSink[E, Z]` — Pre-Process Sink Input
345
+
346
+ The dual of `via`: instead of transforming a stream's output, you pre-process a sink's input. Here is the signature:
347
+
348
+ ```scala
349
+ trait Pipeline[-In, +Out] {
350
+ def andThenSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
351
+ }
352
+ ```
353
+
354
+ This is an alias for `applyToSink`. After calling `andThenSink`, the resulting sink accepts `In` elements, transforms them through the pipeline, and feeds the `Out` elements to the original sink:
355
+
356
+ ```scala
357
+ import zio.blocks.streams.*
358
+
359
+ // A pipeline that normalizes strings
360
+ val normalize = Pipeline.map[String, String](_.trim.toLowerCase)
361
+ // normalize: Pipeline[String, String] = zio.blocks.streams.Pipeline$MapPipeline@2a3c97bf
362
+
363
+ // Apply to different sinks
364
+ val collectNormalized = normalize.andThenSink(Sink.collectAll[String])
365
+ // collectNormalized: Sink[Nothing, String, Chunk[String]] = zio.blocks.streams.Sink$Contramapped@7c1204c4
366
+ val countNormalized = normalize.andThenSink(Sink.count)
367
+ // countNormalized: Sink[Nothing, String, Long] = zio.blocks.streams.Sink$Contramapped@3a99eae2
368
+
369
+ val result = Stream(" Hello ", " WORLD ").run(collectNormalized)
370
+ // result: Either[Nothing, Chunk[String]] = Right(IndexedSeq("hello", "world"))
371
+ ```
372
+
373
+ ### When to Use `andThenSink` vs `via`
374
+
375
+ Both achieve the same result. Choose based on which side you want to reuse:
376
+
377
+ | Approach | Use when… |
378
+ |--------------------------------------|---------------------------------------------|
379
+ | `stream.via(pipe).run(sink)` | You have a fixed pipeline and varying sinks |
380
+ | `stream.run(pipe.andThenSink(sink))` | You want a reusable "pre-processing sink" |
381
+
382
+ The laws guarantee equivalence: `stream.via(pipe).run(sink) == stream.run(pipe.andThenSink(sink))`.
383
+
384
+ ### `Pipeline#applyToSink[E, Z]` — Direct Application
385
+
386
+ `andThenSink` is an alias for `applyToSink`. Here is the signature:
387
+
388
+ ```scala
389
+ trait Pipeline[-In, +Out] {
390
+ def applyToSink[E, Z](sink: Sink[E, Out, Z]): Sink[E, In, Z]
391
+ }
392
+ ```
393
+
394
+ Prefer `andThenSink` for readability.
395
+
396
+ ## JVM Primitive Specialization
397
+
398
+ `Pipeline.map`, `Pipeline.filter`, `Pipeline.collect`, and `Pipeline.identity` all require `JvmType.Infer[A]` implicit parameters. These are resolved at compile time and enable unboxed, specialized code paths for primitive types (`Int`, `Long`, `Float`, `Double`, etc.). You never need to provide these explicitly — the compiler infers them automatically.
399
+
400
+ `Pipeline.take` and `Pipeline.drop` do not require `JvmType.Infer` because they do not inspect or transform element values — they only count positions.
401
+
402
+ ## Integration
403
+
404
+ `Pipeline` integrates seamlessly with the other core streaming primitives: `Stream` and `Sink`. Understanding these integrations shows how pipelines fit into the broader streaming architecture.
405
+
406
+ ### With Stream
407
+
408
+ `Pipeline` is the mechanism behind `Stream.via`. Every call to `stream.via(pipe)` delegates to `pipe.applyToStream(stream)`, which constructs the appropriate `Stream` subtype node. See [Stream — Integration with Pipeline and Sink](./stream.md#integration-with-pipeline-and-sink) for the stream-side perspective.
409
+
410
+ ### With Sink
411
+
412
+ `Pipeline.andThenSink` creates a new `Sink` that pre-processes its input through the pipeline before the original sink consumes it. The `Sink` type provides its own transformation methods (`contramap`, `map`, `mapError`) — `andThenSink` extends these with the full power of pipeline composition (filtering, taking, dropping, collecting).
413
+
414
+ ## Running the Examples
415
+
416
+ All code from this guide is available as runnable examples in the `streams-examples` module.
417
+
418
+ **1. Clone the repository and navigate to the project:**
419
+
420
+ ```bash
421
+ git clone https://github.com/zio/zio-blocks.git
422
+ cd zio-blocks
423
+ ```
424
+
425
+ **2. Run individual examples with sbt:**
426
+
427
+ ### Basic Usage
428
+
429
+ This example demonstrates all six Pipeline factory methods: `map`, `filter`, `collect`, `take`, `drop`, and `identity`. Here is the source code:
430
+
431
+ ```scala title="streams-examples/src/main/scala/pipeline/PipelineBasicUsageExample.scala"
432
+ /*
433
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
434
+ *
435
+ * Licensed under the Apache License, Version 2.0 (the "License");
436
+ * you may not use this file except in compliance with the License.
437
+ * You may obtain a copy of the License at
438
+ *
439
+ * http://www.apache.org/licenses/LICENSE-2.0
440
+ *
441
+ * Unless required by applicable law or agreed to in writing, software
442
+ * distributed under the License is distributed on an "AS IS" BASIS,
443
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
444
+ * See the License for the specific language governing permissions and
445
+ * limitations under the License.
446
+ */
447
+
448
+ package pipeline
449
+
450
+ import zio.blocks.streams.*
451
+ import zio.sbt.ExprEval.show
452
+
453
+ object PipelineBasicUsageExample extends App {
454
+ println("=== Pipeline Basic Usage ===\n")
455
+
456
+ // 1. Pipeline.map — transform each element
457
+ println("1. Pipeline.map — transform each element:")
458
+ val doubler = Pipeline.map[Int, Int](_ * 2)
459
+ show(Stream(1, 2, 3).via(doubler).runCollect)
460
+
461
+ // 2. Pipeline.map — cross-type transformation
462
+ println("\n2. Pipeline.map — type-changing transformation:")
463
+ val intToString = Pipeline.map[Int, String](n => s"item-$n")
464
+ show(Stream(1, 2, 3).via(intToString).runCollect)
465
+
466
+ // 3. Pipeline.filter — keep elements matching a predicate
467
+ println("\n3. Pipeline.filter — keep matching elements:")
468
+ val positives = Pipeline.filter[Int](_ > 0)
469
+ show(Stream(-2, -1, 0, 1, 2).via(positives).runCollect)
470
+
471
+ // 4. Pipeline.collect — partial function (filter + map)
472
+ println("\n4. Pipeline.collect — partial function transformation:")
473
+ val extractInts = Pipeline.collect[Any, Int] { case n: Int => n * 10 }
474
+ show(Stream(1, "a", 2, "b").via(extractInts).runCollect)
475
+
476
+ // 5. Pipeline.take — first n elements
477
+ println("\n5. Pipeline.take — first n elements (short-circuits):")
478
+ val firstThree = Pipeline.take[Int](3)
479
+ show(Stream.range(0, 1000).via(firstThree).runCollect)
480
+
481
+ // 6. Pipeline.drop — skip first n elements
482
+ println("\n6. Pipeline.drop — skip first n elements:")
483
+ val skipTwo = Pipeline.drop[String](2)
484
+ show(Stream("header", "subheader", "data1", "data2").via(skipTwo).runCollect)
485
+
486
+ // 7. Pipeline.identity — pass-through (no-op)
487
+ println("\n7. Pipeline.identity — pass-through (neutral element):")
488
+ val noOp = Pipeline.identity[Int]
489
+ show(Stream(1, 2, 3).via(noOp).runCollect)
490
+
491
+ // 8. Combining drop and take to get a range
492
+ println("\n8. Combining drop and take for pagination:")
493
+ val page2 = Pipeline.drop[Int](3).andThen(Pipeline.take(3))
494
+ show(Stream.range(0, 10).via(page2).runCollect)
495
+ }
496
+ ```
497
+
498
+ ([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineBasicUsageExample.scala))
499
+
500
+ Run it with:
501
+
502
+ ```bash
503
+ sbt "streams-examples/runMain pipeline.PipelineBasicUsageExample"
504
+ ```
505
+
506
+ ### Pipeline Composition
507
+
508
+ This example shows how to compose pipelines with `andThen`, apply them to multiple streams, and build pipelines conditionally. Here is the source code:
509
+
510
+ ```scala title="streams-examples/src/main/scala/pipeline/PipelineCompositionExample.scala"
511
+ /*
512
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
513
+ *
514
+ * Licensed under the Apache License, Version 2.0 (the "License");
515
+ * you may not use this file except in compliance with the License.
516
+ * You may obtain a copy of the License at
517
+ *
518
+ * http://www.apache.org/licenses/LICENSE-2.0
519
+ *
520
+ * Unless required by applicable law or agreed to in writing, software
521
+ * distributed under the License is distributed on an "AS IS" BASIS,
522
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
523
+ * See the License for the specific language governing permissions and
524
+ * limitations under the License.
525
+ */
526
+
527
+ package pipeline
528
+
529
+ import zio.blocks.streams.*
530
+ import zio.sbt.ExprEval.show
531
+
532
+ object PipelineCompositionExample extends App {
533
+ println("=== Pipeline Composition ===\n")
534
+
535
+ // 1. Basic andThen composition
536
+ println("1. Composing filter + map with andThen:")
537
+ val filterPositive = Pipeline.filter[Int](_ > 0)
538
+ val double = Pipeline.map[Int, Int](_ * 2)
539
+ val composed = filterPositive.andThen(double)
540
+
541
+ show(Stream(-3, -1, 0, 2, 5).via(composed).runCollect)
542
+
543
+ // 2. Multi-stage pipeline
544
+ println("\n2. Multi-stage pipeline (filter → map → take):")
545
+ val multiStage = Pipeline
546
+ .filter[Int](_ % 2 == 0)
547
+ .andThen(Pipeline.map[Int, Int](_ * 10))
548
+ .andThen(Pipeline.take(3))
549
+
550
+ show(Stream.range(1, 11).via(multiStage).runCollect)
551
+
552
+ // 3. Reusing the same pipeline across different streams
553
+ println("\n3. Reusing the same pipeline across different streams:")
554
+ val normalize = Pipeline.filter[Int](_ >= 0).andThen(Pipeline.map[Int, Double](_.toDouble / 100.0))
555
+
556
+ val dataset1 = Stream(150, -20, 75, 200, -10)
557
+ val dataset2 = Stream(50, 100, -5, 300)
558
+
559
+ show(dataset1.via(normalize).runCollect)
560
+ show(dataset2.via(normalize).runCollect)
561
+
562
+ // 4. Category law: left identity
563
+ println("\n4. Category law — left identity (identity andThen p == p):")
564
+ val pipe = Pipeline.map[Int, Int](_ + 1)
565
+ val data = Stream(1, 2, 3)
566
+
567
+ val withIdentityL = data.via(Pipeline.identity[Int].andThen(pipe)).runCollect
568
+ val withoutIdentity = data.via(pipe).runCollect
569
+ show(withIdentityL)
570
+ show(withoutIdentity)
571
+
572
+ // 5. Category law: right identity
573
+ println("\n5. Category law — right identity (p andThen identity == p):")
574
+ val withIdentityR = data.via(pipe.andThen(Pipeline.identity[Int])).runCollect
575
+ show(withIdentityR)
576
+ show(withoutIdentity)
577
+
578
+ // 6. Category law: associativity
579
+ println("\n6. Category law — associativity ((p andThen q) andThen r == p andThen (q andThen r)):")
580
+ val p = Pipeline.filter[Int](_ > 0)
581
+ val q = Pipeline.map[Int, Int](_ * 3)
582
+ val r = Pipeline.take[Int](2)
583
+
584
+ val leftGrouped = (p.andThen(q)).andThen(r)
585
+ val rightGrouped = p.andThen(q.andThen(r))
586
+
587
+ val source = Stream(-1, 2, -3, 4, 5, 6)
588
+ show(source.via(leftGrouped).runCollect)
589
+ show(source.via(rightGrouped).runCollect)
590
+
591
+ // 7. Building pipelines conditionally
592
+ println("\n7. Building pipelines conditionally:")
593
+ def buildPipeline(limit: Option[Int], onlyPositive: Boolean): Pipeline[Int, Int] = {
594
+ var pipe: Pipeline[Int, Int] = Pipeline.identity[Int]
595
+ if (onlyPositive) pipe = pipe.andThen(Pipeline.filter(_ > 0))
596
+ limit.foreach(n => pipe = pipe.andThen(Pipeline.take(n.toLong)))
597
+ pipe
598
+ }
599
+
600
+ val conditionalPipe = buildPipeline(limit = Some(3), onlyPositive = true)
601
+ show(Stream(-1, 2, -3, 4, 5, 6).via(conditionalPipe).runCollect)
602
+
603
+ val noPipe = buildPipeline(limit = None, onlyPositive = false)
604
+ show(Stream(-1, 2, -3).via(noPipe).runCollect)
605
+ }
606
+ ```
607
+
608
+ ([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineCompositionExample.scala))
609
+
610
+ Run it with:
611
+
612
+ ```bash
613
+ sbt "streams-examples/runMain pipeline.PipelineCompositionExample"
614
+ ```
615
+
616
+ ### Sink Integration
617
+
618
+ This example demonstrates applying pipelines to sinks with `andThenSink`, showing the equivalence between `stream.via(pipe).run(sink)` and `stream.run(pipe.andThenSink(sink))`. Here is the source code:
619
+
620
+ ```scala title="streams-examples/src/main/scala/pipeline/PipelineSinkIntegrationExample.scala"
621
+ /*
622
+ * Copyright 2024-2026 John A. De Goes and the ZIO Contributors
623
+ *
624
+ * Licensed under the Apache License, Version 2.0 (the "License");
625
+ * you may not use this file except in compliance with the License.
626
+ * You may obtain a copy of the License at
627
+ *
628
+ * http://www.apache.org/licenses/LICENSE-2.0
629
+ *
630
+ * Unless required by applicable law or agreed to in writing, software
631
+ * distributed under the License is distributed on an "AS IS" BASIS,
632
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
633
+ * See the License for the specific language governing permissions and
634
+ * limitations under the License.
635
+ */
636
+
637
+ package pipeline
638
+
639
+ import zio.blocks.streams.*
640
+ import zio.sbt.ExprEval.show
641
+
642
+ object PipelineSinkIntegrationExample extends App {
643
+ println("=== Pipeline ↔ Sink Integration ===\n")
644
+
645
+ // 1. Basic andThenSink usage
646
+ println("1. Basic andThenSink — pre-process before collecting:")
647
+ val doubler = Pipeline.map[Int, Int](_ * 2)
648
+ val collectDoubled = doubler.andThenSink(Sink.collectAll[Int])
649
+
650
+ show(Stream(1, 2, 3).run(collectDoubled))
651
+
652
+ // 2. Equivalence law: via + run == run + andThenSink
653
+ println("\n2. Equivalence law: stream.via(p).run(sink) == stream.run(p.andThenSink(sink)):")
654
+ val pipe = Pipeline.filter[Int](_ > 2).andThen(Pipeline.map[Int, Int](_ * 10))
655
+ val source = Stream(1, 2, 3, 4, 5)
656
+ val sink = Sink.collectAll[Int]
657
+
658
+ val viaResult = source.via(pipe).run(sink)
659
+ val andThenSinkResult = source.run(pipe.andThenSink(sink))
660
+ show(viaResult)
661
+ show(andThenSinkResult)
662
+
663
+ // 3. Reusable pre-processing sink
664
+ println("\n3. Reusable pre-processing sink:")
665
+ val cleanString = Pipeline.map[String, String](_.trim.toLowerCase)
666
+ val collectCleaned = cleanString.andThenSink(Sink.collectAll[String])
667
+ val countCleaned = cleanString.andThenSink(Sink.count)
668
+
669
+ val rawData = Stream(" Hello ", " WORLD ", " Scala ")
670
+
671
+ show(rawData.run(collectCleaned))
672
+ show(rawData.run(countCleaned))
673
+
674
+ // 4. andThenSink with foldLeft
675
+ println("\n4. Pipeline + foldLeft sink:")
676
+ val sumPositives = Pipeline
677
+ .filter[Int](_ > 0)
678
+ .andThenSink(Sink.foldLeft(0)((acc, x) => acc + x))
679
+
680
+ show(Stream(-5, 3, -2, 7, 1).run(sumPositives))
681
+
682
+ // 5. andThenSink with head/find
683
+ println("\n5. Pipeline + head/find sinks:")
684
+ val firstEven = Pipeline.filter[Int](_ % 2 == 0).andThenSink(Sink.head[Int])
685
+
686
+ show(Stream(1, 3, 4, 6).run(firstEven))
687
+
688
+ // 6. Multiple pipelines, same sink
689
+ println("\n6. Multiple pipelines applied to the same sink:")
690
+ val baseSink = Sink.collectAll[Int]
691
+
692
+ val evens = Pipeline.filter[Int](_ % 2 == 0).andThenSink(baseSink)
693
+ val odds = Pipeline.filter[Int](_ % 2 != 0).andThenSink(baseSink)
694
+
695
+ val nums = Stream(1, 2, 3, 4, 5, 6)
696
+ show(nums.run(evens))
697
+ show(nums.run(odds))
698
+
699
+ // 7. Complex pipeline applied to sink
700
+ println("\n7. Multi-stage pipeline applied to sink:")
701
+ val processingPipe = Pipeline
702
+ .filter[Int](_ > 0)
703
+ .andThen(Pipeline.map[Int, Int](_ * 2))
704
+ .andThen(Pipeline.take(3))
705
+
706
+ val processedSum = processingPipe.andThenSink(Sink.foldLeft(0)(_ + _))
707
+
708
+ show(Stream(-1, 5, 3, 8, 2, 9).run(processedSum))
709
+ }
710
+ ```
711
+
712
+ ([source](https://github.com/zio/zio-blocks/blob/main/streams-examples/src/main/scala/pipeline/PipelineSinkIntegrationExample.scala))
713
+
714
+ Run it with:
715
+
716
+ ```bash
717
+ sbt "streams-examples/runMain pipeline.PipelineSinkIntegrationExample"
718
+ ```