@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,651 @@
1
+ ---
2
+ id: async
3
+ title: "Async"
4
+ ---
5
+
6
+ The `async` module provides `Async[A]`, a lightweight, zero-dependency
7
+ asynchronous effect type for modern Scala. It is designed around a single idea:
8
+ **a ready `Async[A]` is just an `A`**. The happy path allocates nothing — no
9
+ effect tree, no wrapper, no boxing beyond what a generic JVM method already
10
+ requires — so synchronous code composed with `map` / `flatMap` runs at
11
+ hand-written speed while still being able to suspend on genuinely asynchronous
12
+ work.
13
+
14
+ ## Overview
15
+
16
+ `Async[A]` is an opaque type whose ready representation is the value itself and
17
+ whose pending representation is a `Pollable[A]`. You never construct it
18
+ directly; you enter the type through constructors and transform it through
19
+ extension methods:
20
+
21
+ - **Constructors** — `Async.succeed`, `Async.fail`, `Async.attempt`,
22
+ `Async.never`, `Async.collectAll`, and the callback bridge `Async.promise`.
23
+ - **Transformers** — `map`, `flatMap`, `zip`, `zipWith`, `catchAll`,
24
+ `mapError`, `orElse`, `foldCause`, `either`, `tap`, `ensuring`, `as`, `unit`,
25
+ `*>`, `<*`, `flatten`, and the conditional helpers `when` / `unless`.
26
+ - **Direct style** — `Async.async { ... .await ... }` lets you write
27
+ straight-line code with `.await`, rewritten at compile time into a
28
+ non-blocking `flatMap` chain.
29
+ - **Running** — `.block` drives an `Async` to its value (blocking on the JVM,
30
+ throwing on a genuinely pending value on JS).
31
+ - **Interop** — conversions to and from `scala.concurrent.Future` on every
32
+ platform, Java's `CompletionStage` / `CompletableFuture` on the JVM, and
33
+ `js.Promise` on Scala.js.
34
+
35
+ On Scala 3 the transformers are zero-cost `inline` extension methods (the ready
36
+ path applies your function directly to the underlying value with no `Function1`
37
+ allocation). On Scala 2 they are methods on an implicit `AsyncOps` class. The
38
+ raw-value representation — a ready `Async[A]` *is* an `A` — holds identically on
39
+ both.
40
+
41
+ ## Runnable example
42
+
43
+ The [`async-examples`](https://github.com/zio/zio-blocks/tree/main/async-examples)
44
+ module contains a single self-contained program that walks through the major
45
+ features in one file — ready-path composition, direct-style `Async.async` /
46
+ `.await`, `zip` / `collectAll`, error handling, `Async.promise`, a custom
47
+ [[Pollable]] leaf, `tap` / `ensuring`, cancellable `Async.start` /
48
+ `Async.Running`, and JVM `Future` interop.
49
+
50
+ ```bash
51
+ sbt "++3.8.3; async-examples/run"
52
+ ```
53
+
54
+ The program models a small order-fulfillment pipeline: fetch a user and order,
55
+ check warehouse stock, pack a shipment, and audit the steps. The structure is
56
+ intentionally linear so you can read it top-to-bottom as a tutorial.
57
+
58
+ ### Direct-style fulfillment
59
+
60
+ The heart of the demo is straight-line code over suspending steps — no
61
+ callback nesting, no manual `flatMap` chains:
62
+
63
+ ```scala
64
+ def fulfill(orderId: Int): Async[Shipment] = Async.async {
65
+ val order = fetchOrder(orderId).await
66
+ val lines = order.items.map { item =>
67
+ val stock = stockFor(item.sku).await
68
+ if (stock.onHand < item.qty)
69
+ throw new IllegalStateException(s"short ${item.sku}")
70
+ (item.sku, item.qty)
71
+ }
72
+ Shipment(orderId, lines, carrier = "zio-blocks-express")
73
+ }
74
+ ```
75
+
76
+ A failure from any `.await` short-circuits the block as a failed `Async`, the
77
+ same as throwing inside synchronous code.
78
+
79
+ ### Callback bridge
80
+
81
+ Legacy APIs that take success/error callbacks lift cleanly through
82
+ `Async.promise` (Scala 3 context-function style):
83
+
84
+ ```scala
85
+ val json: Async[String] =
86
+ Async.promise[String] {
87
+ // Capture the completer — nested callbacks do not inherit the `?=>` context.
88
+ val completer = summon[Completer[String]]
89
+ legacyHttpGet("/users/42", completer)
90
+ }
91
+ ```
92
+
93
+ ### Custom asynchronous leaves
94
+
95
+ When you need a bespoke source of suspension — a socket read, a timer, a
96
+ foreign runtime — implement [[Pollable]] and return it from `flatMap` to
97
+ **sequence** it, or store it via `Async.succeed` / `map` to keep it as a
98
+ **value** (the runtime wraps pollable success values so combinators never
99
+ mistake them for suspended computations; note that the top-level drivers —
100
+ `.block`, `Async.start`, and the interop converters — do drive a directly
101
+ stored pollable for its effects at delivery, settling to the pollable itself).
102
+ The showcase includes a `Delayed` pollable that becomes ready after a few
103
+ scheduler ticks.
104
+
105
+ See
106
+ [`AsyncShowcaseExample.scala`](https://github.com/zio/zio-blocks/blob/main/async-examples/src/main/scala/async/AsyncShowcaseExample.scala)
107
+ for the full program.
108
+
109
+ ## Installation
110
+
111
+ Add the following to your `build.sbt`:
112
+
113
+ ```sbt
114
+ libraryDependencies += "dev.zio" %% "zio-blocks-async" % "0.0.51"
115
+ ```
116
+
117
+ For cross-platform projects (Scala.js):
118
+
119
+ ```sbt
120
+ libraryDependencies += "dev.zio" %%% "zio-blocks-async" % "0.0.51"
121
+ ```
122
+
123
+ Supported platforms: JVM and Scala.js, Scala 2.13 and Scala 3.x. The
124
+ direct-style `Async.async` block is rewritten by dotty-cps-async on Scala 3
125
+ (JVM and older Scala 3 JS), by a hybrid backend on Scala 3.8+ JS (native
126
+ `js.async` / `js.await` for direct-position awaits, with the dotty-cps-async
127
+ transform as fallback for awaits inside closures, by-name arguments, or nested
128
+ methods), and by a hand-written macro on Scala 2.
129
+
130
+ ## Constructors
131
+
132
+ `Async.succeed` lifts a pure value; `Async.fail` lifts an error; `Async.attempt`
133
+ catches a thrown exception and turns it into a failure.
134
+
135
+ ```scala
136
+ import zio.blocks.async._
137
+
138
+ val ready: Async[Int] = Async.succeed(42)
139
+ // ready: Async[Int] = 42
140
+
141
+ val failed: Async[Nothing] = Async.fail(new RuntimeException("boom"))
142
+ // failed: Async[Nothing] = zio.blocks.async.Failure@12260721
143
+
144
+ val caught: Async[Int] = Async.attempt(Integer.parseInt("123"))
145
+ // caught: Async[Int] = 123
146
+ ```
147
+
148
+ `Async.collectAll` sequences a collection of `Async` values, short-circuiting on
149
+ the first failure:
150
+
151
+ ```scala
152
+ val all: Async[List[Int]] =
153
+ Async.collectAll(List(Async.succeed(1), Async.succeed(2), Async.succeed(3)))
154
+ // all: Async[List[Int]] = List(1, 2, 3)
155
+ ```
156
+
157
+ ## Evaluation model: eager up to suspension
158
+
159
+ `Async` is **eager**, not a lazy `IO`. Constructing an `Async` performs all of
160
+ its synchronous work immediately — building the value *runs* it, up to the first
161
+ point where it genuinely has to wait:
162
+
163
+ - `Async.attempt(body)` runs `body` now (on the calling thread); `Async.promise`
164
+ runs its setup block now; `Async.async { ... }` runs its synchronous prefix
165
+ (and any **ready** `.await`s) now; `succeed(x).map(f)` runs `f` now. Only a
166
+ combinator applied to an **already-suspended** value defers — its function
167
+ runs when the value is later driven.
168
+ - The single genuinely-lazy primitive is a custom [`Pollable`](#low-level-building-blocks-pollable):
169
+ its `poll` runs only when a driver asks for the value. Suspension exists only
170
+ *downstream of* an unresolved `poll`.
171
+
172
+ This makes the success/ready path allocation-free (no effect tree, no per-step
173
+ thunk) — the source of its throughput — at the cost of referential transparency
174
+ (building has effects) and cancel-by-drop (use [`Cancelable.cancel`](#eager-cancellable-running-asyncstart-and-asyncrunning)
175
+ instead). It sits next to `scala.concurrent.Future` (also eager) rather than
176
+ cats-effect `IO` / ZIO (lazy).
177
+
178
+ ### What happens at a pending suspension differs by platform — by design
179
+
180
+ Once an `Async` hits a genuinely **pending** suspension (an await of a
181
+ not-yet-complete value), what advances it follows each platform's *fastest*
182
+ suspension mechanism, so the two platforms diverge:
183
+
184
+ - **JVM (and Scala.js on Scala 3 < 3.8, and Scala 2):** the value is a
185
+ poll-driven `Pollable` with no ambient driver. The continuation after the
186
+ pending suspension runs only when an external driver polls it — `.block`,
187
+ `fa.start`, or an interop runner (`toFuture` / `unsafeRunAsync`). A built-but-
188
+ never-driven block leaves that continuation un-run.
189
+ - **Scala.js on Scala 3.8+:** `Async.async`/`.await` compile to native
190
+ `js.async` / `js.await` — a real JavaScript async function whose driver *is*
191
+ the event loop. Once the awaited value settles, the continuation self-resumes
192
+ off the microtask queue even if nothing polls the `Async`. This is the same
193
+ event-loop driving that makes await-heavy direct-style blocks substantially
194
+ faster than the dotty-cps-async backend, so the behavior is intentional, not a
195
+ defect: it is the zero-cost default of the fastest JS suspension primitive.
196
+
197
+ In practice this is invisible — you always drive an `Async` you build — and the
198
+ *value* is identical on every cell. The divergence is observable only by a block
199
+ that is constructed, has its awaited value settle, and is then never driven.
200
+
201
+ ## Transforming values
202
+
203
+ On the ready path the transformers apply your function directly to the
204
+ underlying value; only a genuinely pending `Async` takes the suspended slow
205
+ path.
206
+
207
+ ```scala
208
+ val mapped: Async[Int] = Async.succeed(20).map(_ + 1)
209
+ // mapped: Async[Int] = 21
210
+
211
+ val chained: Async[Int] = Async.succeed(20).flatMap(n => Async.succeed(n * 2))
212
+ // chained: Async[Int] = 40
213
+
214
+ val recovered: Async[Int] =
215
+ Async.fail(new RuntimeException("nope")).catchAll(_ => Async.succeed(-1))
216
+ // recovered: Async[Int] = -1
217
+ ```
218
+
219
+ ### Combining with `zip`
220
+
221
+ `zip` fuses two `Async` values into a tuple using the `combinators` module's
222
+ `Tuples` combiner, so chained zips flatten automatically (`a zip b zip c`
223
+ yields `Async[(A, B, C)]`, not `Async[((A, B), C)]`). Use `zipWith` to combine
224
+ with an explicit function:
225
+
226
+ ```scala
227
+ val zipped: Async[(Int, String)] =
228
+ Async.succeed(1).zip(Async.succeed("two"))
229
+ // zipped: Async[Tuple2[Int, String]] = (1, "two")
230
+
231
+ val summed: Async[Int] =
232
+ Async.succeed(3).zipWith(Async.succeed(4))(_ + _)
233
+ // summed: Async[Int] = 7
234
+ ```
235
+
236
+ ### Error handling
237
+
238
+ `catchAll` recovers a failure, `mapError` transforms the cause, `orElse`
239
+ falls back to another `Async`, and `either` reifies the outcome:
240
+
241
+ ```scala
242
+ val asEither: Async[Either[Throwable, Int]] =
243
+ Async.fail(new RuntimeException("x")).either
244
+ // asEither: Async[Either[Throwable, Int]] = Left(
245
+ // java.lang.RuntimeException: x
246
+ // )
247
+
248
+ val fallback: Async[Int] =
249
+ Async.fail(new RuntimeException("x")).orElse(Async.succeed(0))
250
+ // fallback: Async[Int] = 0
251
+ ```
252
+
253
+ ### Conditional effects
254
+
255
+ `when` / `unless` run an `Async` only when a condition holds, discarding its
256
+ value. `Async.never` is an `Async` that never completes — useful as a sentinel:
257
+
258
+ ```scala
259
+ val maybe: Async[Unit] = when(1 < 2)(Async.succeed(()))
260
+ // maybe: Async[Unit] = ()
261
+
262
+ val skipped: Async[Unit] = unless(1 < 2)(Async.succeed(()))
263
+ // skipped: Async[Unit] = ()
264
+
265
+ val forever: Async[Nothing] = Async.never
266
+ // forever: Async[Nothing] = zio.blocks.async.Async$$anon$1@65db325f
267
+ ```
268
+
269
+ ## Direct style: `Async.async` and `.await`
270
+
271
+ Inside an `Async.async { ... }` block you can write straight-line code and use
272
+ `.await` to extract the value of any `Async`. The block is rewritten at compile
273
+ time into a non-blocking `flatMap` / `map` chain — there is no thread blocking
274
+ on the happy path. `.await` is **lexically restricted** to `Async.async`
275
+ blocks; using it elsewhere is a compile error.
276
+
277
+ ```scala
278
+ def loadUser(id: Int): Async[String] = Async.succeed(s"user-$id")
279
+ def loadOrders(user: String): Async[List[String]] = Async.succeed(List(s"$user-order"))
280
+
281
+ val program: Async[Int] =
282
+ Async.async {
283
+ val user = loadUser(1).await
284
+ val orders = loadOrders(user).await
285
+ orders.size
286
+ }
287
+ ```
288
+
289
+ A failure encountered by `.await` short-circuits the block and surfaces as a
290
+ failed `Async`, exactly as if you had thrown — `Async.async { Async.fail(t).await }`
291
+ is equivalent to `Async.fail(t)`.
292
+
293
+ `.await` is also supported inside the higher-order-function closures of the
294
+ standard strict collections — `List`, `Option`, `Vector`, immutable `Set`,
295
+ immutable `Map`, `Array`, immutable `Queue`, and immutable `ArraySeq` — across a
296
+ broad set of methods (`map` / `foreach` / `flatMap`, the predicate scans
297
+ `find` / `exists` / `forall` / `filter` / `filterNot`, the folds
298
+ `foldLeft` / `foldRight` / `reduce` / `reduceLeft`, the prefix scans
299
+ `takeWhile` / `dropWhile`, and `collect`). Each is detailed below, with semantics
300
+ that match the method's natural meaning (and the Scala 3 backends exactly). A few
301
+ positions diverge between Scala 2 and Scala 3 — those are called out explicitly as
302
+ **Divergence** notes:
303
+
304
+ - **`List.map`** is **eager**: strict `map` applies the closure to every element
305
+ first — running all construction-time side effects — producing a
306
+ `List[Async[B]]`, and the awaits are then sequenced left-to-right via
307
+ `Async.collectAll` (fail-fast on the first failure). This mirrors how
308
+ `Array.map(async ...)` composes in JavaScript.
309
+ - **`List.foreach`** is **lazy / sequential**: the closure for element `n+1` runs
310
+ only after element `n`'s `.await` completes successfully, and a failed await
311
+ short-circuits the remaining elements. The result is `Unit`.
312
+ - **`List.flatMap`** is **lazy / sequential** like `foreach`, but accumulates each
313
+ closure's `IterableOnce` into the result `List`.
314
+ - **`Option.map` / `Option.flatMap` / `Option.foreach`**: an `Option` holds at
315
+ most one element, so the eager/lazy distinction collapses to a single
316
+ `Some`/`None` branch — `None` short-circuits (the closure never runs), `Some(x)`
317
+ runs the closure and (for `map`/`flatMap`) rewraps the result; a failed await
318
+ propagates.
319
+ - **`Vector` / immutable `Set` / immutable `Queue` / immutable `ArraySeq`**
320
+ (`map` / `flatMap` / `foreach`, plus the builder-backed methods below): **lazy
321
+ / sequential** like `List.foreach` (the closure for element `n+1` runs only
322
+ after element `n`'s await completes; a failure short-circuits the rest). Note
323
+ that `Vector.map` / `Queue.map` / `ArraySeq.map` are lazy — only `List.map` is
324
+ eager (it is the special case backed by dotty-cps-async's `ListAsyncShift`).
325
+ The result **collection type is preserved** (`Vector.map` → `Vector`,
326
+ `Queue.map` → `Queue`, `ArraySeq.map` → `ArraySeq`, `Set.map` → `Set`); for
327
+ `Set`, the *awaited* values are deduplicated.
328
+ - **`Array`** (`map` / `flatMap` / `foreach` / `filter` / `takeWhile` /
329
+ `dropWhile` / `foldLeft` / `collect` / `find` / `exists` / `forall`): the
330
+ result is always an `Array[B]` with the **element type preserved, including
331
+ primitives** (e.g. `Array[Int].map(_.toLong)` → a primitive `Array[Long]`).
332
+ `Array.map` is **eager** like `List.map` (a failing await still runs every
333
+ preceding closure); `Array.flatMap` and the rest are **lazy / sequential**.
334
+ The result-building HOFs (`map` / `flatMap` / `filter` / `takeWhile` /
335
+ `collect`) rebuild via `Array.newBuilder`, which needs a `ClassTag[B]` — the
336
+ same one the user's own `Array` HOF already required, so it resolves for any
337
+ concrete element type (an abstract/path-dependent `B` without a `ClassTag` in
338
+ scope is not supported, exactly as the standard-library call would not be).
339
+ - **immutable `Map`** (`map` / `flatMap` / `foreach`): **lazy / sequential** over
340
+ the map's `(K, V)` entries. A pair-returning `map`/`flatMap` rebuilds a
341
+ `Map[K2, V2]` (later entries with the same key win); a non-pair `map`/`flatMap`
342
+ widens the result to an `Iterable`, matching the standard library's overload
343
+ choice. `foreach` runs the closure for each entry, returning `Unit`.
344
+ - **Short-circuiting predicate scans** (`find` / `exists` / `forall`, predicate
345
+ `A => Boolean`): **lazy / sequential** over any whitelisted receiver — the
346
+ predicate for element `n+1` runs only after element `n`'s await completes, and
347
+ the scan stops at the first decisive element (`exists` → first `true`; `forall`
348
+ → first `false`; `find` → first matching element as `Some`, else `None`).
349
+ `Option.find` is covered on every cell too — on Scala 2 it resolves via the
350
+ `Option`→`Iterable` implicit conversion, which the macro recognizes specifically
351
+ for `find`.
352
+ - **`foldLeft`** (op `(B, A) => B`): **lazy / sequential** over any whitelisted
353
+ receiver via `.iterator` — a left fold is inherently sequential (element
354
+ `n+1`'s op needs `n`'s accumulator), so the op for element `n+1` runs only
355
+ after element `n`'s await completes, and a failed await short-circuits the
356
+ rest. The accumulator is threaded through and `foldLeft[B]` returns `B`
357
+ directly (it may differ from the element type), so awaits in the initial
358
+ accumulator are sequenced before the fold.
359
+ - **`reduce` / `reduceLeft`** (op `(B, A) => B`): **lazy / sequential** over any
360
+ whitelisted receiver via `.iterator` — `foldLeft` seeded by the FIRST element
361
+ instead of an initial value, so the op for element `n+1` runs only after
362
+ element `n`'s await completes and a failed await short-circuits the rest. A
363
+ single-element receiver returns that element without running the op; an EMPTY
364
+ receiver fails with `UnsupportedOperationException` (catchable via `catchAll`,
365
+ rethrown by `.block`).
366
+ - **`foldRight`** (op `(A, B) => B`): **lazy / sequential** but
367
+ **right-associative** — `op(x1, op(x2, ..., op(xn, z)))` — so the op for the
368
+ RIGHTMOST element runs first (the receiver is materialized and drained in
369
+ reverse to keep the await-ordering correct). An empty receiver yields the
370
+ initial accumulator (the op never runs); a failed await short-circuits the
371
+ remaining (right-to-left) elements.
372
+ - **`filter` / `filterNot`** (predicate `A => Boolean`): **lazy / sequential**
373
+ over a `List` / `Vector` / `Array` / immutable `Set` / immutable `Queue` /
374
+ immutable `ArraySeq` / `Option` — the predicate for element `n+1` runs only
375
+ after element `n`'s await completes, and a failed await short-circuits the
376
+ rest. The result **collection type is preserved** (`filter` keeps elements
377
+ whose predicate is `true`, `filterNot` those whose predicate is `false`).
378
+ **Divergence:** `Map.filter` / `Map.filterNot` with `.await` is a
379
+ **Scala-2-only superset** — dotty-cps-async has no working `MapOpsAsyncShift.filter`
380
+ and rejects it on Scala 3.
381
+ - **`takeWhile` / `dropWhile`** (predicate `A => Boolean`): **lazy / sequential**
382
+ over an ordered receiver (`List` / `Vector` / immutable `Queue` / immutable
383
+ `ArraySeq` / `Array`) — these are **prefix-ordered**, so the predicate for
384
+ element `n+1` runs only after element `n`'s await completes, and the FIRST
385
+ element whose predicate is `false` decides the boundary (`takeWhile` keeps the
386
+ leading run and discards it and the rest; `dropWhile` drops the leading run and
387
+ keeps it and the rest **unconditionally**, never re-evaluating the predicate).
388
+ A failed await short-circuits the rest. The result **collection type is
389
+ preserved**. They are restricted to ordered receivers because a leading-prefix
390
+ predicate is ill-defined on an unordered `Set` / `Map` (and `Option` does not
391
+ provide them); the Scala 2 macro rejects those with an actionable compile
392
+ error.
393
+ - **`collect`** (partial function `{ case ... }`): **lazy / sequential** over a
394
+ `List` / `Vector` / `Array` / immutable `Set` / immutable `Queue` / immutable
395
+ `ArraySeq` — keeps the elements the partial function is defined at, mapping
396
+ each through its (awaiting) case body; the case for element `n+1` runs only
397
+ after element `n`'s await completes, and a failed await short-circuits the
398
+ rest. The result **collection type is preserved**. An `Option` receiver is
399
+ supported too: `None` short-circuits without evaluating the partial function,
400
+ `Some(a)` yields `Some(b)` if a case matches, else `None`. A **non-pair
401
+ `Map.collect`** (whose case bodies yield a `B`, so the result is an
402
+ `Iterable[B]`) is supported on every cell. The case guard runs exactly once per
403
+ element (Scala 2). A `.await` in a case GUARD is rejected.
404
+ **Divergence:** a **pair-yielding `Map.collect`** (whose case bodies yield
405
+ `(K2, V2)` pairs, so the result is a `Map[K2, V2]`) is **unsupported on every
406
+ cell** — dotty-cps-async has only an `IterableOpsAsyncShift.collect[F, B]`
407
+ shift (no Map-specific one), so the `Map`-returning overload is a compile error
408
+ on Scala 3, and the Scala 2 macro rejects it to stay at parity. Rewrite it as
409
+ `m.toVector.collect { case ... => k -> v.await }.toMap`.
410
+
411
+ These behave identically across Scala 2/3 and JVM/JS **except** for the handful of
412
+ positions flagged **Divergence** above (`Map.filter` / `filterNot` is a
413
+ Scala-2-only superset; a pair-yielding `Map.collect` is unsupported everywhere).
414
+ Because Scala desugars
415
+ for-comprehensions over a `List` / `Option` / `Vector` / `Set` / `Map` into these
416
+ methods,
417
+ single- and multi-generator `for` comprehensions with `.await` work too
418
+ (`for ... yield` → `map`; nested generators → `flatMap`/`map`; `for { ... }`
419
+ without `yield` → `foreach`; a guard `if` → `withFilter`):
420
+
421
+ ```scala
422
+ val pairs: Async[List[Int]] = Async.async {
423
+ for {
424
+ i <- List(1, 2)
425
+ j <- List(10, 20)
426
+ } yield Async.succeed(i + j).await
427
+ } // List(11, 21, 12, 22)
428
+ ```
429
+
430
+ > **Scala 2 limitation (current):** the Scala 2 macro supports `.await` in
431
+ > sequential statements, `if` / `match` / `while` / `try`-`catch`-`finally`,
432
+ > `throw`, assignments, `List` / `Option` / `Vector` / `Array` / immutable `Set` /
433
+ > immutable `Queue` / immutable `ArraySeq` / immutable
434
+ > `Map` `map` / `foreach` / `flatMap` closures, the short-circuiting predicate
435
+ > scans `find` / `exists` / `forall`, `filter` / `filterNot`, `foldLeft`,
436
+ > `foldRight`, and `reduce` / `reduceLeft` over
437
+ > those receivers, the prefix-ordered `takeWhile` / `dropWhile` over ordered
438
+ > receivers (`List` / `Vector` / immutable `Queue` / immutable `ArraySeq` /
439
+ > `Array`), `collect` over builder-backed receivers (`List` / `Vector` / `Array`
440
+ > / immutable `Set` / immutable `Queue` / immutable `ArraySeq`), and the
441
+ > for-comprehensions that desugar to the former (including guards), but **rejects**
442
+ > `.await` inside other function
443
+ > literals / higher-order-function arguments (and HOFs over collections other than
444
+ > those whitelisted families), with an actionable compile error. Those positions
445
+ > are supported on Scala 3. The whitelisted set above is the **final, stable**
446
+ > Scala 2 contract for the standard strict collections; positions outside it are
447
+ > intentionally unsupported on Scala 2 (a custom collection, or `.await` inside an
448
+ > arbitrary user lambda passed to a third-party HOF, cannot be rewritten without a
449
+ > shift typeclass the Scala 2 macro deliberately does not depend on).
450
+ >
451
+ > Conversely, the Scala 2 macro is a strict superset for some guard shapes that
452
+ > dotty-cps-async on Scala 3 currently rejects: *multiple* `List`
453
+ > for-comprehension guards (chained `withFilter`), and *any* `Option`
454
+ > for-comprehension guard (DCA has no `AsyncShift[Option#WithFilter]`). Single
455
+ > `List` guards behave identically on every cell.
456
+
457
+ ## The callback bridge: `Async.promise`
458
+
459
+ `Async.promise` builds an `Async` from a callback-style API. You receive a
460
+ `Completer` and call `succeed` / `fail` when the result arrives. Completion is
461
+ one-shot — the first `succeed` or `fail` wins; later calls are silent no-ops. If
462
+ the body completes the completer synchronously, the result collapses to a bare
463
+ value with no `Pollable` allocation.
464
+
465
+ On Scala 3 the completer is supplied via a context function, so you can call the
466
+ top-level `succeed` / `fail` helpers directly:
467
+
468
+ ```scala
469
+ import zio.blocks.async._
470
+
471
+ val fromCallback: Async[Int] =
472
+ Async.promise[Int] {
473
+ // register a callback with some external system, then:
474
+ succeed(42)
475
+ }
476
+ ```
477
+
478
+ On Scala 2 the body receives the `Completer` explicitly; mark it `implicit` to
479
+ use the same top-level `succeed` / `fail` helpers, or call its methods directly:
480
+
481
+ ```scala
482
+ // Scala 2
483
+ Async.promise[Int] { implicit c => succeed(42) }
484
+ Async.promise[Int] { c => c.succeed(42) }
485
+ ```
486
+
487
+ ## Running an `Async`
488
+
489
+ `.block` drives an `Async` to its value. A ready value returns immediately. A
490
+ pending value blocks the calling thread on the JVM (Loom-friendly) and throws
491
+ on JS, where the platform cannot block. Use `.block` only at the edge of your
492
+ program, never on a scheduler/reactor thread.
493
+
494
+ ```scala
495
+ val result: Int = Async.succeed(20).map(_ + 1).block
496
+ // result: Int = 21
497
+ ```
498
+
499
+ ### Eager, cancellable running: `Async.start` and `Async.Running`
500
+
501
+ The `fa.start` extension eagerly drives an already-built `Async` without
502
+ blocking and returns a `Running[A]` handle — itself an `Async[A]` you can poll,
503
+ compose, or cancel. Compose with `either`, `tap`, `foldCause`, and the other
504
+ operators **before** `start` to observe or transform the outcome:
505
+
506
+ ```scala
507
+ import zio.blocks.async._
508
+
509
+ val running: Async.Running[Either[Throwable, Int]] =
510
+ Async.succeed(1).map(_ + 1).either.tap {
511
+ case Right(value) => Async.succeed(println(s"done: $value"))
512
+ case Left(cause) => Async.succeed(println(s"failed: $cause"))
513
+ }.start
514
+
515
+ running.cancel() // idempotent; no-op once the run has completed
516
+ ```
517
+
518
+ The companion `Async.start(body)` is a single by-name method that evaluates
519
+ `body` on a background worker (JVM) or microtask (JS) and returns a `Running`
520
+ for the result — the `Async` analogue of `Future.apply`. It captures a throwing
521
+ body (even a statically `Nothing`-typed one such as `Async.start(sys.error(...))`)
522
+ as a failed run rather than letting it escape at the call site. (Driving an
523
+ existing `Async` value is the `fa.start` extension above, not `Async.start(fa)`,
524
+ which would treat `fa` as a by-name body to evaluate.)
525
+
526
+ For an already-ready `Async`, observers composed before `start` run synchronously
527
+ on the calling thread. For a suspended `Async`, driving proceeds on a daemon
528
+ worker thread on the JVM, or via microtasks on Scala.js. `cancel()` is
529
+ driver-level only: it stops the poll loop and suppresses publishing a terminal
530
+ value, but does not abort an in-flight leaf (socket, timer, JS promise).
531
+
532
+ #### Fanning one `Async` out to several consumers
533
+
534
+ To deliver one `Async`'s result to multiple consumers, **start it once and share
535
+ the `Running` handle** — `Running` publishes its outcome through an atomic, so
536
+ the underlying `Async` (and any side effects in `map`/`flatMap`/`tap`) is driven
537
+ exactly once no matter how many consumers poll, block, or compose on the handle:
538
+
539
+ ```scala
540
+ import zio.blocks.async._
541
+
542
+ val shared: Async.Running[Int] = Async.succeed(1).map(_ + 1).start
543
+ val a: Int = shared.block // both observe the one result;
544
+ val b: Int = shared.block // the `+ 1` ran once, on the worker
545
+ ```
546
+
547
+ Do **not** instead drive the same raw `Async` from two places at once (two
548
+ separate `fa.start`s on the same `fa`, or `fa.start` racing `fa.block`). On the
549
+ JVM that polls the same combinator concurrently, which is **undefined**: a
550
+ `map`/`flatMap`/`tap` function may run more than once, and a `collectAll` batch
551
+ may observe its drain buffer mid-update. This matches `Pollable`'s contract that
552
+ re-polling a settled value is undefined and platform-specific — it cannot arise
553
+ on single-threaded Scala.js. Sequential re-use (re-polling or composing after an
554
+ earlier drive has settled) is fine; only *concurrent* re-driving of the raw
555
+ value is not.
556
+
557
+ ## Interop
558
+
559
+ `Async` converts to and from the platform's standard async types, preserving
560
+ both success and failure. Ingress lives on the `Async` companion
561
+ (`Async.fromFuture`, `Async.fromCompletionStage`, `Async.fromJsPromise`) and
562
+ egress lives on extension methods (`fa.toFuture`, `fa.toCompletableFuture`,
563
+ `fa.toJsPromise`). `scala.concurrent.Future` conversion is available on both
564
+ platforms; the JVM additionally offers Java `CompletionStage` /
565
+ `CompletableFuture`, and Scala.js offers `js.Promise`.
566
+
567
+ On the JVM:
568
+
569
+ ```scala
570
+ import zio.blocks.async._
571
+ import scala.concurrent.{ExecutionContext, Future}
572
+ import java.util.concurrent.CompletableFuture
573
+
574
+ implicit val ec: ExecutionContext = ExecutionContext.global
575
+
576
+ val fromFut: Async[Int] = Async.fromFuture(Future.successful(1))
577
+ val toFut: Future[Int] = Async.succeed(1).toFuture
578
+ val fromStage: Async[Int] = Async.fromCompletionStage(CompletableFuture.completedFuture(1))
579
+ val toStage: CompletableFuture[Int] = Async.succeed(1).toCompletableFuture
580
+ ```
581
+
582
+ On Scala.js, the companion provides `Async.fromFuture` / `Async.fromJsPromise`
583
+ and the egress extensions provide `fa.toFuture` / `fa.toJsPromise` for native
584
+ `scala.scalajs.js.Promise` interop.
585
+
586
+ ## Low-level building blocks: `Pollable`
587
+
588
+ Most code should use the constructors and `Async.promise`. For custom
589
+ asynchronous leaves you can implement a `Pollable[A]` directly. `poll(onComplete)`
590
+ returns the ready value (or a `Failure`) when available, or a `Pollable`
591
+ (commonly `this`, optionally a replacement representing the rest of the
592
+ computation — drivers and combinators direct their next poll at whichever
593
+ pollable was returned) when still pending; a pending pollable must arrange to
594
+ call `onComplete.run()` once progress can be made, prompting the scheduler to
595
+ re-poll.
596
+
597
+ ### Combinator chain depth
598
+
599
+ Combinator continuations poll their children recursively without a trampoline
600
+ (a deliberate trade: the poll path stays allocation- and indirection-free). The
601
+ determinant of depth-safety is whether driving has to unwind a deep chain of
602
+ combinators in **receiver position** over a value that is still **pending** —
603
+ not whether the chain was written iteratively or recursively:
604
+
605
+ - **Over a ready source, `flatMap` / `map` / `zipWith` chains are depth-safe to
606
+ any length.** When the receiver is already a value, each step resolves
607
+ eagerly and collapses — no `Pollable` is retained — so `var fa = …;
608
+ while (…) fa = fa.flatMap(g)` (and the `.map` form) consume constant stack
609
+ regardless of length (verified into the millions).
610
+ - **Over a pending source, a deep receiver-position chain is NOT depth-safe.**
611
+ When the receiver stays pending, each `fa.flatMap(g)` / `fa.map(g)` /
612
+ `fa.zipWith(...)` wraps the previous pending value, so driving descends one
613
+ stack frame per level before anything settles and overflows around
614
+ default-JVM-stack depths of a few tens of thousands (~50–100k). This is true
615
+ for **both** a recursive shape (`def loop(n) = src.flatMap(_ => loop(n-1))`)
616
+ **and** an iterative accumulation (`fa = fa.flatMap(_ => src.flatMap(...))`) —
617
+ the syntax doesn't matter, the pending receiver spine does.
618
+ - **`Async.collectAll` and direct-style `Async.async` `while` loops are
619
+ depth-safe even over pending sources.** `collectAll` is a single `Pollable`
620
+ that iterates its elements internally (no receiver spine), and the
621
+ `Async.async` loop rewrite advances one iteration per driver poll rather than
622
+ pre-building a deep chain — both verified at 200k+ pending steps.
623
+
624
+ So: to sequence a large, *pending-heavy* workload, reach for `collectAll` or an
625
+ `Async.async` `while` loop, not a hand-built `flatMap`/`map`/`zipWith` tower over
626
+ a pending value. (`Future` avoids this overflow for any shape only because it
627
+ bounces every `flatMap` through its `ExecutionContext`; `Async` skips that hop
628
+ for speed and accepts the depth bound instead.)
629
+
630
+ ## Cross-platform and cross-version notes
631
+
632
+ | Feature | JVM | JS | Scala 2.13 | Scala 3.x | Notes |
633
+ |----------------------------------|-----|----|------------|-----------|---------------------------------------------------------|
634
+ | Constructors & transformers | ✅ | ✅ | ✅ | ✅ | Identical behavior everywhere |
635
+ | `Async.async` / `.await` | ✅ | ✅ | ✅ | ✅ | DCA (Scala 3), native `js.async`/`js.await` for direct-position awaits with DCA fallback for closure/by-name awaits (3.8+ JS), macro (Scala 2); `.await` in the standard strict-collection HOF closures (`List` / `Option` / `Vector` / `Set` / `Map` / `Array` / `Queue` / `ArraySeq`: `map`/`foreach`/`flatMap`/`filter`/`collect`/`fold*`/`reduce*`/`takeWhile`/`dropWhile`/`find`/`exists`/`forall`) and their for-comprehensions is supported on every cell, except a few explicitly-noted divergences (`Map.filter` Scala-2-only; a pair-yielding `Map.collect` unsupported everywhere) — see the HOF section above |
636
+ | `.block` on a pending value | ✅ | ❌ | ✅ | ✅ | Blocks on JVM; throws on JS (cannot block) |
637
+ | `Async.start` / `Async.Running` | ✅ | ✅ | ✅ | ✅ | Eager non-blocking runner; worker thread (JVM) / microtask (JS) |
638
+ | `Future` interop | ✅ | ✅ | ✅ | ✅ | `Async.fromFuture` / `fa.toFuture` on both platforms |
639
+ | `CompletionStage` interop | ✅ | ❌ | ✅ | ✅ | JVM-only (`fromCompletionStage` / `toCompletableFuture`) |
640
+ | `js.Promise` interop | ❌ | ✅ | ✅ | ✅ | JS-only (`fromJsPromise` / `toJsPromise`) |
641
+
642
+ The core `Async` API is identical across all platforms and Scala versions by
643
+ design; platform interop APIs are intentionally platform-specific as shown
644
+ above. The cross-platform test suite fails if any user-visible core behavior
645
+ diverges.
646
+
647
+ ## See Also
648
+
649
+ - [Runnable example](#runnable-example) — `async-examples` single-file showcase
650
+ - [Combinators](./combinators.md) — `Async#zip` uses the `Tuples` combiner for
651
+ automatic tuple flattening.