@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,35 @@
1
+ ---
2
+ id: index
3
+ title: "Execution and Compatibility"
4
+ description: "Execution and Compatibility index: async execution, primitive lanes, platform availability, and Scala 2 support for streams."
5
+ keywords:
6
+ - "Asynchronous Streams"
7
+ - "Primitive Specialization"
8
+ - "Cross-Platform Streams"
9
+ - "Scala 2 Compatibility"
10
+ sidebar_label: "Execution and Compatibility"
11
+ ---
12
+
13
+ [Core Types](../core/index.md) covers the descriptions you write and [Low-Level Primitives](../primitives/index.md) the machinery they compile into. This section covers neither: four concerns that cut across every `Stream`, `Pipeline`, and `Sink` once one starts running. None adds a type to compose with.
14
+
15
+ ## Asynchronous Stream Execution
16
+
17
+ [Asynchronous Stream Execution](./async-execution.md) — how one `Stream[E, A]` describes both synchronous and asynchronous pipelines, with no second type or mode parameter to track. Covers the async constructors, operators, and `*Async` terminals, plus cancellation and resource ownership.
18
+
19
+ ## Zero-Boxing Optimization
20
+
21
+ [Zero-Boxing Optimization](./zero-boxing.md) — how the library dispatches on a primitive element type to select a physical lane, and how each lane signals end of stream. A structural account of the dispatch mechanism, not a benchmark.
22
+
23
+ ## Platform Differences: JVM and Scala.js
24
+
25
+ [Platform Differences](./platform-differences.md) — which members are JVM-only, which are cross-platform, and what to write instead when a blocking terminal is unavailable on Scala.js. Read it before cross-building.
26
+
27
+ ## Scala 2 Compatibility Design Note
28
+
29
+ [Scala 2 Compatibility Design Note](./scala-2-compatibility.md) — why streams supports Scala 2.13, the hot-path constraint that shaped the approach, and why the two versions share sources rather than splitting them.
30
+
31
+ ## See Also
32
+
33
+ - [Streams Reference](../index.md) — module overview
34
+ - [Core Types](../core/index.md) — `Stream`, `Pipeline`, and `Sink`
35
+ - [Low-Level Primitives](../primitives/index.md) — `Reader` and `Writer`
@@ -0,0 +1,297 @@
1
+ ---
2
+ id: platform-differences
3
+ title: "Platform Differences: JVM and Scala.js"
4
+ sidebar_label: "Platform Differences"
5
+ description: "Which stream members exist on the JVM, which exist on Scala.js, and the cross-platform replacement for every blocking terminal."
6
+ keywords:
7
+ - "Cross-Platform Streams"
8
+ - "Scala.js Support"
9
+ - "Blocking Terminals"
10
+ - "Availability Matrix"
11
+ - "StreamPlatformSpecific"
12
+ ---
13
+
14
+ The terminals whose names end in `Async` are the cross-platform API: they compile and run on the JVM and on Scala.js alike. The blocking terminals — `Stream#run`, `Stream#runCollect`, `Stream#start` and their siblings — exist only on the JVM. Code that cross-builds is written against the `*Async` family, and there is no configuration flag, shim, or runtime fallback that changes that.
15
+
16
+ The split is a compile-time one. A Scala.js source that calls `Stream#runCollect` does not fail when it runs; it fails to compile, because that member does not exist on that platform.
17
+
18
+ ## Availability Matrix
19
+
20
+ Nineteen members behave differently across the two platforms. The JVM column gives what the member evaluates to there, the Scala.js column gives what happens instead, and the last column names what to write in shared source:
21
+
22
+ | Member | JVM result | Scala.js | Cross-platform replacement |
23
+ |-----------------------------|---------------------------------|------------------------------|----------------------------------------------|
24
+ | `Stream#count` | `Either[E, Long]` | Absent — compile error | `Stream#countAsync` |
25
+ | `Stream#exists` | `Either[E, Boolean]` | Absent — compile error | `Stream#existsAsync` |
26
+ | `Stream#find` | `Either[E, Option[A]]` | Absent — compile error | `Stream#findAsync` |
27
+ | `Stream#forall` | `Either[E, Boolean]` | Absent — compile error | `Stream#forallAsync` |
28
+ | `Stream#foreach` | `Either[E, Unit]` | Absent — compile error | `Stream#foreachAsync` |
29
+ | `Stream#head` | `Either[E, Option[A]]` | Absent — compile error | `Stream#headAsync` |
30
+ | `Stream#last` | `Either[E, Option[A]]` | Absent — compile error | `Stream#lastAsync` |
31
+ | `Stream#run` | `Either[E3, Z]` | Absent — compile error | `Stream#runAsync` |
32
+ | `Stream#runCollect` | `Either[E, Chunk[A]]` | Absent — compile error | `Stream#runCollectAsync` |
33
+ | `Stream#runDrain` | `Either[E, Unit]` | Absent — compile error | `Stream#runDrainAsync` |
34
+ | `Stream#runFold(z: Double)` | `Either[E, Double]` | Absent — compile error | `Stream#runFoldAsync(z: Double)` |
35
+ | `Stream#runFold(z: Int)` | `Either[E, Int]` | Absent — compile error | `Stream#runFoldAsync(z: Int)` |
36
+ | `Stream#runFold(z: Long)` | `Either[E, Long]` | Absent — compile error | `Stream#runFoldAsync(z: Long)` |
37
+ | `Stream#runFold(z: Z)` | `Either[E, Z]` | Absent — compile error | `Stream#runFoldAsync(z: Z)` |
38
+ | `Stream#runForeach` | `Either[E, Unit]` | Absent — compile error | `Stream#runForeachAsync` |
39
+ | `Stream#start` | `scope.$[Reader.SyncReader[A]]` | Absent — compile error | `Stream#startAsync`, `Stream#useReaderAsync` |
40
+ | `Sink.create` | `Sink[E, A, Z]` | Absent — compile error | `Sink.createAsync`, `Sink.createBoth` |
41
+ | `Reader.AsyncReader#toSync` | `Reader.SyncReader[A]` | Absent — compile error | None; drive the `AsyncReader` itself |
42
+ | `Async#block` | `A`, parking the thread | Present — throws at run time | `Async#map`, `Async#flatMap`, `Async.start` |
43
+
44
+ Three details in that table repay a second look. The replacements that take a callback take an *effectful* one: `Stream#exists` takes `A => Boolean` while `Stream#existsAsync` takes `A => Async[Boolean]`, and the same shift applies to `Stream#findAsync`, `Stream#forallAsync`, `Stream#foreachAsync`, `Stream#runForeachAsync`, and every `Stream#runFoldAsync` overload. The asynchronous fold family is also one overload wider than the blocking one — `Stream#runFoldAsync(z: Float)` has no blocking twin, so the `Float` accumulator lane is reachable only through the cross-platform API. And `Async#block` is the single row whose Scala.js cell says "run time" rather than "compile error", because it is declared in shared source and cannot be removed from the platform it cannot serve.
45
+
46
+ Porting a blocking call is a rename plus a change of result type, since every replacement wraps its answer in `Async`:
47
+
48
+ ```scala
49
+ import zio.blocks.streams._
50
+ import zio.blocks.async._
51
+
52
+ // JVM only: Either[Nothing, Chunk[Int]]
53
+ val onTheJvm = Stream(1, 2, 3).map(_ * 2).runCollect
54
+
55
+ // JVM and Scala.js: Async[Either[Nothing, Chunk[Int]]]
56
+ val everywhere = Stream(1, 2, 3).map(_ * 2).runCollectAsync
57
+ ```
58
+
59
+ [Asynchronous Stream Execution](./async-execution.md) documents the full `*Async` surface, including the [`Async[Either[E, Z]]` convention](./async-execution.md#the-asynceithere-z-convention) that the second line above returns.
60
+
61
+ ## Why Blocking Terminals Are JVM-Only
62
+
63
+ A blocking terminal makes one promise: when it returns, the pipeline has finished and the answer is in hand as an `Either`. On the JVM that promise is kept by parking the calling thread until the pipeline completes, and some other thread does the completing.
64
+
65
+ JavaScript has no other thread. Its single execution thread is also the thread that would have to run the callbacks that deliver the result, so a terminal that waited for completion would prevent the completion it waits for. There is no implementation of `Stream#runCollect` on Scala.js that both blocks and terminates.
66
+
67
+ That leaves two ways to express the constraint. The blocking terminals could stay in shared source and throw on Scala.js, or they could be removed from the Scala.js API surface entirely. The library removed them. The cost is that a cross-building source cannot always be compiled unchanged; the benefit is that the compiler reports it, at the call site, before anything ships.
68
+
69
+ `Async#block` is the one member that had to take the other route, because it is an extension method in shared `async` source with no stream terminal to hide behind. On Scala.js it returns normally when the effect has already completed, and throws `IllegalStateException` the moment a suspension actually has to wait — the message tells the caller to drive the `Pollable` from a non-blocking entry point instead.
70
+
71
+ ## `StreamPlatformSpecific`
72
+
73
+ The sixteen relocated `Stream` members are not scattered through the platform trees. They live in one trait per platform, `StreamPlatformSpecific[+E, +A]`, which `Stream[E, A]` mixes in under a self-type:
74
+
75
+ ```
76
+ ┌────────────────────────────────────────────────────────────────┐
77
+ │ streams/shared one Stream[E, A], one Sink, one Reader │
78
+ │ every *Async terminal lives here and compiles on both │
79
+ └────────────────────────────────┬───────────────────────────────┘
80
+ │ mixed into Stream by self-type
81
+ ┌────────────────┴────────────────┐
82
+ ▼ ▼
83
+ ┌──────────────────────────────┐ ┌──────────────────────────────┐
84
+ │ streams/jvm │ │ streams/js │
85
+ │ StreamPlatformSpecific: │ │ StreamPlatformSpecific: │
86
+ │ 16 blocking members │ │ empty trait, 0 members │
87
+ │ Sink.create │ │ no Sink.create │
88
+ │ AsyncReader#toSync │ │ no AsyncReader#toSync │
89
+ └──────────────────────────────┘ └──────────────────────────────┘
90
+ a blocking terminal parks a blocking terminal is not a
91
+ a thread and returns Either member: it fails to compile
92
+ ```
93
+
94
+ On the JVM the trait carries the whole blocking family, each member delegating to a blocking run of a `Sink`:
95
+
96
+ ```scala
97
+ trait StreamPlatformSpecific[+E, +A] { self: Stream[E, A] =>
98
+ def count: Either[E, Long]
99
+ def exists(pred: A => Boolean): Either[E, Boolean]
100
+ def find(pred: A => Boolean): Either[E, Option[A]]
101
+ def forall(pred: A => Boolean): Either[E, Boolean]
102
+ def foreach(f: A => Unit): Either[E, Unit]
103
+ def head: Either[E, Option[A]]
104
+ def last: Either[E, Option[A]]
105
+ def run[ES, E3, Z](sink: Sink[ES, A, Z])(implicit
106
+ errorConcat: Concat.WithOut[E, ES, E3]
107
+ ): Either[E3, Z]
108
+ def runCollect: Either[E, Chunk[A]]
109
+ def runDrain: Either[E, Unit]
110
+ def runFold(z: Double)(f: (Double, A) => Double): Either[E, Double]
111
+ def runFold(z: Int)(f: (Int, A) => Int): Either[E, Int]
112
+ def runFold(z: Long)(f: (Long, A) => Long): Either[E, Long]
113
+ def runFold[Z](z: Z)(f: (Z, A) => Z)(implicit jtZ: JvmType.Infer[Z]): Either[E, Z]
114
+ def runForeach(f: A => Unit): Either[E, Unit]
115
+ def start(implicit scope: Scope): scope.$[Reader.SyncReader[A]]
116
+ }
117
+ ```
118
+
119
+ The Scala.js counterpart is the same trait name, the same type parameters, and the same self-type, with nothing inside it:
120
+
121
+ ```scala
122
+ trait StreamPlatformSpecific[+E, +A] { self: Stream[E, A] => }
123
+ ```
124
+
125
+ That empty body is the whole mechanism. `Stream` still mixes the trait in on Scala.js, so no type signature anywhere in shared source changes; the sixteen names simply have no definition to resolve to, and the compiler reports each one as "not a member".
126
+
127
+ ## `Sink.create` and Custom Sinks
128
+
129
+ Custom sinks follow the same rule as terminals, because a custom sink's callback is handed a reader and a reader is where the two execution modes part company. `Sink.create` is declared in `SinkCompanionPlatformSpecific`, which the `Sink` companion extends, and it exists only in the JVM copy of that trait:
130
+
131
+ ```scala
132
+ object Sink {
133
+ // JVM only — the callback consumes a blocking SyncReader
134
+ def create[E, A, Z](f: Reader.SyncReader[A] => Z): Sink[E, A, Z]
135
+
136
+ // JVM and Scala.js
137
+ def createAsync[E, A, Z](f: Reader.AsyncReader[A] => Async[Z]): Sink[E, A, Z]
138
+ def createBoth[E, A, Z](
139
+ sync: Reader.SyncReader[A] => Z,
140
+ async: Reader.AsyncReader[A] => Async[Z]
141
+ ): Sink[E, A, Z]
142
+ }
143
+ ```
144
+
145
+ Note the parameter type of `Sink.create`: it is `Reader.SyncReader[A]`, not the wider `Reader[A]`. The callback is guaranteed a synchronous reader, and when a `Sink` built this way is driven from an asynchronous terminal the JVM implementation bridges by calling `Reader.AsyncReader#toSync` — the member that does not exist on Scala.js either.
146
+
147
+ To write the same aggregation for both platforms, use `Sink.createAsync` and sequence the reader's pulls with `Async#flatMap` instead of a loop:
148
+
149
+ ```scala
150
+ import zio.blocks.streams._
151
+ import zio.blocks.streams.io.Reader
152
+ import zio.blocks.async._
153
+
154
+ val average: Sink[Nothing, Int, Double] =
155
+ Sink.createAsync[Nothing, Int, Double] { reader =>
156
+ def loop(sum: Long, count: Long): Async[(Long, Long)] =
157
+ reader.readInt(Long.MinValue).flatMap { value =>
158
+ if (value == Long.MinValue) Async.succeed((sum, count))
159
+ else loop(sum + value, count + 1L)
160
+ }
161
+
162
+ loop(0L, 0L).map { case (sum, count) =>
163
+ if (count == 0L) 0.0 else sum.toDouble / count
164
+ }
165
+ }
166
+ ```
167
+
168
+ `Sink.createBoth` is the option to reach for when the synchronous drain is worth keeping: it takes both callbacks, and the terminal selects exactly one of them, so the JVM keeps its direct blocking loop — with no per-pull `Async` to allocate and resume — while Scala.js gets a working implementation. Both callbacks must agree on how much input they consume and what they produce. [Sink](../core/sink.md) documents the reader protocol these callbacks drive.
169
+
170
+ ## Manual Pull Across Platforms
171
+
172
+ Handing the reader to a protocol loop rather than a sink runs into the same split, and here the replacement changes shape rather than just its name. On the JVM, `Stream#start` is `Scope`-based and yields a synchronous reader:
173
+
174
+ ```scala
175
+ trait StreamPlatformSpecific[+E, +A] { self: Stream[E, A] =>
176
+ def start(implicit scope: Scope): scope.$[Reader.SyncReader[A]]
177
+ }
178
+ ```
179
+
180
+ The reader is allocated into the enclosing [`Scope`](../../resource-management/scope.md) as an acquire-release resource, so closing the scope closes the reader, and the dependent result type `scope.$[Reader.SyncReader[A]]` keeps it from escaping that scope. The return type is `Reader.SyncReader[A]`: `Stream#start` never hands back the `Reader` union, because asynchronous boundaries inside the pipeline are bridged by the JVM runtime before you see it.
181
+
182
+ The cross-platform pair is `Stream#startAsync` and `Stream#useReaderAsync`, and they differ from `Stream#start` and from each other in who closes the reader:
183
+
184
+ ```scala
185
+ abstract class Stream[+E, +A] {
186
+ def startAsync: Async[Reader.AsyncReader[A]]
187
+ def useReaderAsync[Z](f: Reader.AsyncReader[A] => Async[Z]): Async[Z]
188
+ }
189
+ ```
190
+
191
+ `Stream#startAsync` transfers ownership to the caller, who must await `close()`; `Stream#useReaderAsync` retains it, passing the reader to `f` and awaiting its close on every outcome. The scoped one is the direct analogue of `Stream#start`, so it is the one to reach for when porting:
192
+
193
+ ```scala
194
+ import zio.blocks.streams._
195
+ import zio.blocks.streams.io.Reader
196
+ import zio.blocks.async._
197
+ import zio.blocks.chunk.Chunk
198
+
199
+ val collected: Async[Chunk[Int]] =
200
+ Stream(1, 2, 3).useReaderAsync[Chunk[Int]](reader => reader.readAll[Int]())
201
+ ```
202
+
203
+ One constraint carries over from the asynchronous reader contract and has no synchronous counterpart: an `AsyncReader` allows at most one operation in flight at a time. Await each `Async` before beginning the next. [Manual Pull and Ownership](./async-execution.md#manual-pull-and-ownership) covers the ownership rules in full.
204
+
205
+ ## Concurrency and Threading
206
+
207
+ Underneath the availability split sits a capability split: the JVM has threads and Scala.js does not. That difference decides how the concurrent operators execute, and how a suspended `Async` is resumed.
208
+
209
+ ### The JVM
210
+
211
+ Concurrent stream operators run their workers on virtual threads when the runtime provides them. `Platform.startVirtualThread` obtains `Thread.ofVirtual()` reflectively, so the module compiles and runs against any supported JDK and uses virtual threads on JDK 21 and later; when the reflective lookup fails for any reason — an older JDK, a security restriction, a linkage error — it falls back to starting a named daemon platform thread rather than failing class initialization.
212
+
213
+ Workers are named, which makes them identifiable in a thread dump or profiler. The `Stream#mapPar` family names its workers `zio-blocks-mappar-worker-<n>-<index>` and its coordinator `zio-blocks-mappar-coordinator-<n>`, where `<n>` is drawn from a counter shared by every thread that lane's reader class starts and `<index>` identifies the worker within one reader.
214
+
215
+ ### Scala.js
216
+
217
+ `Platform.supportsConcurrency` is `false` on Scala.js and `Platform.startVirtualThread` throws `UnsupportedOperationException`. The observable effect is that `Stream#mapPar`, `Stream#flatMapPar`, and `Stream.mergeAll` do not overlap any work: there is no second execution context for work to overlap on.
218
+
219
+ The mechanism is not what the scaladoc on those three operators says. They do not simply become `Stream#map` and `Stream#flatMap` on Scala.js. The factories that build the concurrent readers, `Platform.createMergeReaderFromReader` and `Platform.createMapParReaderFromReader`, route to the same shared concurrent engine on Scala.js as they do on the JVM — merge unconditionally, and `Stream#mapPar` whenever the upstream compiles to an asynchronous reader, with the mapping function wrapped in an immediately-ready effect:
220
+
221
+ ```scala
222
+ internal.AsyncConcurrentReaders.mapPar(reader, n, (a: A) => zio.blocks.async.Async.succeed(f(a)), outType)
223
+ ```
224
+
225
+ The genuinely sequential implementations exist only on the synchronous lane, and are reached when the pipeline compiles end to end to a `SyncReader`: there `Stream#mapPar` becomes a plain mapped reader and merge becomes a flat-mapped one. That single case is what the scaladoc describes, stated as if it were the whole story.
226
+
227
+ The consequence matters more than the plumbing. Wherever the concurrent engine runs, **unordered arrival is retained on Scala.js**. `Stream#mapPar` and `Stream#flatMapPar` are documented to emit in completion order rather than input order, and that stays true on a single-threaded platform. Do not write code that relies on Scala.js restoring source order.
228
+
229
+ :::warning[The scaladoc is stale here]
230
+ The scaladoc on `Stream#mapPar`, `Stream#flatMapPar`, and `Stream.mergeAll` states that on Scala.js each "degrades to sequential `map`" or "sequential `flatMap`". Treat that as a statement about throughput, not about ordering. Relying on it for element order is a bug that will not reproduce on the JVM.
231
+ :::
232
+
233
+ ### The Async Execution Model
234
+
235
+ Under the streams layer, the `async` module resumes suspended computations differently on each platform. On the JVM, a suspended run executes as a serialized sequence of tasks on `ForkJoinPool.commonPool()`; only `Async.start(body)` spawns a thread of its own, a daemon thread named `zio-blocks-async-eval`; and `Async#block` parks the caller with `LockSupport` until the result arrives.
236
+
237
+ On Scala.js, resumptions are queued as microtasks with a `setTimeout(0)` macrotask escape hatch and a ready-resumption limit of 1024, so a long chain of already-complete steps yields to the event loop instead of starving it. `Async#block` throws, as described above. There is no blocking-operations API in the `async` module on either platform — nothing corresponding to a `blocking` executor or an `attemptBlocking` wrapper exists to be looked for.
238
+
239
+ [Async](../../async.md) documents both execution models in detail; this page states only the part that decides what compiles where.
240
+
241
+ ## Platform Capabilities
242
+
243
+ The capability surface is deliberately small. `Platform` is a trait in shared source with one flag, one thread starter, and three reader factories, and the `Platform` object extends the platform-specific implementation of it, so call sites use the same `Platform.*` names regardless of platform:
244
+
245
+ ```scala
246
+ trait Platform {
247
+ def supportsConcurrency: Boolean
248
+ def startVirtualThread(name: String, task: Runnable): Thread
249
+ def createBufferedReaderFromReader[A](upstream: Reader[A], bufferSize: Int): Reader[A]
250
+ def createMergeReaderFromReader[A](
251
+ outerReader: Reader[?],
252
+ maxOpen: Int,
253
+ bufferSize: Int,
254
+ elemType: JvmType
255
+ ): Reader[A]
256
+ def createMapParReaderFromReader[A, B](
257
+ upstream: Reader[A],
258
+ n: Int,
259
+ f: A => B,
260
+ bufferSize: Int,
261
+ inType: JvmType,
262
+ outType: JvmType
263
+ ): Reader[B]
264
+ }
265
+
266
+ object Platform extends PlatformSpecific
267
+ ```
268
+
269
+ Two `final` convenience methods, `Platform.createBufferedReader` and `Platform.createMapParReader`, narrow the corresponding factory's result to `Reader.SyncReader`. They add no capability of their own.
270
+
271
+ What `Platform` does *not* expose is worth stating, because it is the shape of query a cross-platform codebase tends to reach for first. There is no parallelism count, no executor or execution-context accessor, and no `isJS` flag. `Platform.supportsConcurrency` is the only capability flag, and the only supported way to ask at run time whether concurrent work will actually overlap.
272
+
273
+ ## Enforcement
274
+
275
+ The placement of the blocking API is not maintained by convention; it is verified by a negative compilation test that asserts each blocking member is *absent* on Scala.js. Scala 3 asserts this in-band, using `scala.compiletime.testing.typeCheckErrors` to require that the compiler rejects each snippet with a "not a member" error:
276
+
277
+ ```scala
278
+ private inline def missingMember(inline code: String, member: String): Boolean =
279
+ typeCheckErrors(code).exists(error => error.message.contains(member) && error.message.contains("not a member"))
280
+ ```
281
+
282
+ The spec covers `Reader.AsyncReader#toSync` (both directly and through wildcard imports, so a re-export cannot reintroduce it), `Stream#run`, `Stream#runCollect`, `Stream#start`, and `Sink.create`. Scala 2 has no `typeCheckErrors`, so those five references live in a fixture at `streams/js/src/test/negative-scala-2/BlockingApiPlacement.scala`, alongside two further references — `Writer#concatAsync` and `Writer#contramapAsync` — that name members `Writer` has on neither platform. That path sits outside the normal test source directories on purpose: it is not compiled by the ordinary Scala 2 test run, and no build task in this repository currently wires it in, so on Scala 2 the placement is documented by the fixture rather than enforced by it.
283
+
284
+ For the reader the point is a guarantee rather than a test detail: a cross-platform source that uses a blocking terminal fails at compile time on Scala.js. It will not build a Scala.js artifact that throws in a browser, and it will not pass a JVM build and then fail a JS one for a reason that only shows up under load.
285
+
286
+ ## Scala 2 Versus Scala 3
287
+
288
+ There is no public-API difference between Scala 2.13 and Scala 3 in the streams module. Each version tree holds exactly two files, `LowPriorityJvmTypeInferPlatform.scala` and `internal/InternalVersionSpecific.scala`, and both declare `private[streams]` traits — implementation detail for `JvmType` inference and for primitive pull loops, invisible from user code.
289
+
290
+ The axis that changes what you can call is JVM versus Scala.js, which is what the [availability matrix](#availability-matrix) above records. [Scala 2 Compatibility](./scala-2-compatibility.md) covers the syntax differences that do affect how you write against the API, such as wildcard imports and implicit resolution.
291
+
292
+ ## See Also
293
+
294
+ - [Asynchronous Stream Execution](./async-execution.md) — the full cross-platform `*Async` API
295
+ - [Reader](../primitives/reader.md#from-native-asynchronous-sources) — the JVM NIO and Scala.js `ReadableStream` adapters
296
+ - [Bounded Concurrency](../core/stream.md#bounded-concurrency) — `Stream#mapPar`, `Stream#flatMapPar`, `Stream.mergeAll`, and `Stream#mapParAsync`
297
+ - [Async](../../async.md) — the effect type and both execution models
@@ -0,0 +1,88 @@
1
+ ---
2
+ id: scala-2-compatibility
3
+ title: "Scala 2 Compatibility Design Note"
4
+ sidebar_label: "Scala 2 Compatibility"
5
+ description: "Why streams supports Scala 2.13, the hot-path constraint that shaped the design, and why the sources are shared rather than split per version."
6
+ keywords:
7
+ - "Scala 2 Compatibility"
8
+ - "Cross Compilation"
9
+ - "Hot Path Performance"
10
+ - "Shared Source Set"
11
+ - "JvmType Inference"
12
+ ---
13
+
14
+ This document explains the design of Scala 2.13 support for `zio.blocks.streams` and the constraints that shaped it.
15
+
16
+ ## Motivation
17
+
18
+ HTTP data types in `zio-blocks` depend on streams. `zio-http` 4 depends on those HTTP data types. Without Scala 2 stream support, `zio-http` cannot offer Scala 2 support. That dependency chain makes Scala 2.13 support for streams a hard requirement, not an optional nicety.
19
+
20
+ ## Non-negotiable Constraint: Scala 3 Performance
21
+
22
+ When Scala 2 support was first proposed, the streams implementation used Scala 3 features on hot combinator paths, especially in `Stream` and `Sink` methods that participate in specialization, error-channel elimination, and zero-boxing-friendly code generation. The `inline` keyword on performance-sensitive helpers was not cosmetic; it directly affected what the JVM saw.
23
+
24
+ A Scala 2 compatibility layer is only acceptable if it leaves the Scala 3 hot path structurally unchanged. Concretely, this rules out:
25
+
26
+ - Moving key instance methods out of the Scala 3 class body into a shared trait
27
+ - Removing or weakening `inline` definitions to satisfy Scala 2's lack of that feature
28
+ - Introducing extra trait boundaries that alter the generated Scala 3 bytecode
29
+
30
+ Adapting surface syntax from Scala 3 `using` to Scala 2 `implicit` is fine where it does not touch the hot path. The risk is not the spelling of contextual parameters; the risk is changing the runtime shape of `Stream` and `Sink` under Scala 3.
31
+
32
+ ## Rejected Approach: Version-specific Trait Extraction
33
+
34
+ An early draft extracted several instance methods into `StreamVersionSpecific` and `SinkVersionSpecific` traits under `scala-2/` and `scala-3/`, with the shared classes extending those traits. The methods moved or routed through these traits included:
35
+
36
+ - `Stream.++`, `Stream.catchAll`, `Stream.catchDefect`, `Stream.concat`
37
+ - `Stream.flatMap`, `Stream.mapError`, `Stream.orElse`, `Stream.&&`
38
+ - `Sink.mapError`
39
+
40
+ This shape localized the syntax differences neatly, but changed the structure of the Scala 3 hot path enough to produce measurable regressions. Benchmarks run with `streams-benchmark` on JDK 25 (recorded against Scala 3.8.3; the project targets Scala 3.9.0):
41
+
42
+ | Benchmark | Baseline (`main`) | Trait-extraction draft | Change |
43
+ |---|---:|---:|---|
44
+ | `StreamPipelineBench.zb_flatMap` | 14924.328 ops/s | 1178.997 ops/s | ~12x regression |
45
+ | `StreamPipelineBench.zb_concat` | 26003.818 ops/s | 22044.497 ops/s | regression |
46
+ | `StreamPipelineBench.zb_filterMap` | 54812.709 ops/s | 50349.471 ops/s | regression |
47
+
48
+ The `flatMap` result is the clearest signal. Dropping from roughly 14.9k ops/s to 1.18k ops/s is not an acceptable tradeoff for any compatibility layer. The approach was rejected.
49
+
50
+ ## Current Structure: One Shared Source Set
51
+
52
+ Streams compiles from a single shared source set. `Stream`, `Sink`, `Reader`, `Writer`, and `Pipeline` each have exactly one implementation, under `streams/shared/src/main/scala/`, and the same bytes compile for Scala 2.13 and Scala 3.
53
+
54
+ What made that possible is that the hot combinator paths no longer use any Scala 3-only construct. There is no `inline def` or `inline val`, no `using` or `given`, no `extension`, `enum`, or `opaque type` anywhere in the module's main sources. The constraint above was met not by mirroring the hot path into two trees but by writing it in the syntax both compilers accept, which leaves nothing for a Scala 2 tree to fork.
55
+
56
+ Two files remain version-specific, and each exists under both `scala-2/` and `scala-3/`:
57
+
58
+ ```
59
+ streams/shared/src/main/
60
+ ├── scala/ the whole public API: Stream, Sink, Reader, Writer, Pipeline
61
+ ├── scala-2/zio/blocks/streams/
62
+ │ ├── LowPriorityJvmTypeInferPlatform.scala
63
+ │ └── internal/InternalVersionSpecific.scala
64
+ └── scala-3/zio/blocks/streams/
65
+ ├── LowPriorityJvmTypeInferPlatform.scala
66
+ └── internal/InternalVersionSpecific.scala
67
+ ```
68
+
69
+ Both declare `private[streams]` traits and neither is reachable from user code. `LowPriorityJvmTypeInferPlatform` supplies the lowest-priority `JvmType.Infer[A]` fallback that sends an unrecognized element type to the boxed lane, mixed into `JvmType.Infer`; see [Zero-Boxing](./zero-boxing.md) for the lane machinery it serves. `InternalVersionSpecific` supplies the `pullInt`, `pullLong`, `pullFloat`, and `pullDouble` helpers that read one element from a `Reader.SyncReader` on its primitive lane.
70
+
71
+ Each pair is currently byte-identical, so the surviving split is directory-only: it is a place where a per-version difference could be expressed, not a difference that exists today. Nobody maintains two implementations of anything.
72
+
73
+ There is no public-API difference between Scala 2.13 and Scala 3 in streams, as [Platform Differences](./platform-differences.md#scala-2-versus-scala-3) records. What differs for you is surface syntax you write anyway — a wildcard import is `_` rather than `*`, and a contextual parameter is `implicit` rather than `using`. The axis that actually changes which members exist is JVM versus Scala.js, covered by the [availability matrix](./platform-differences.md#availability-matrix).
74
+
75
+ ## Maintenance Notes
76
+
77
+ Any change to the behavior or public API of `Stream`, `Sink`, `Reader`, `Writer`, or `Pipeline` goes to the shared source under `streams/shared/src/main/scala/`. There is no second tree to mirror it into.
78
+
79
+ Keep the shared sources inside the syntax both compilers accept. An `inline def`, a `using` clause, or an `extension` method added there compiles under Scala 3 and breaks the published Scala 2.13 build, and the constraint above rules out recovering by forking the affected method into two trees.
80
+
81
+ Touch a file under `scala-2/` or `scala-3/` only when you intend a genuine per-version difference, and then change the counterpart in the same commit. Because each pair is byte-identical today, editing one alone is a divergence rather than a fix, and nothing in the build will tell you that you meant it.
82
+
83
+ When making changes that touch hot combinators, re-run `streams-benchmark` and verify that `zb_flatMap`, `zb_concat`, and `zb_filterMap` do not regress relative to the `main` baseline.
84
+
85
+ ## See Also
86
+
87
+ - [Platform Differences](./platform-differences.md) — the JVM versus Scala.js availability matrix, and the Scala 2 versus Scala 3 summary
88
+ - [Zero-Boxing](./zero-boxing.md) — `JvmType.Infer` and the primitive lanes that `LowPriorityJvmTypeInferPlatform` backs