@zio.dev/zio-blocks 0.0.51 → 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 (164) 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 +6 -0
  4. package/guides/getting-started-with-mux.md +0 -112
  5. package/guides/query-dsl-extending.md +1 -1
  6. package/guides/query-dsl-fluent-builder.md +1 -1
  7. package/guides/query-dsl-reified-optics.md +1 -1
  8. package/guides/query-dsl-sql.md +395 -1
  9. package/guides/sql-checked-interpolation.md +173 -0
  10. package/guides/sql-transactions.md +286 -0
  11. package/guides/telemetry-guide.md +131 -70
  12. package/guides/zio-schema-migration.md +6 -6
  13. package/index.md +200 -583
  14. package/package.json +1 -1
  15. package/reference/async.md +1379 -531
  16. package/reference/chunk.md +3 -3
  17. package/reference/codegen/index.md +1 -1
  18. package/reference/combinators.md +4 -4
  19. package/reference/config/config-decoder.md +460 -0
  20. package/reference/config/config-source.md +489 -0
  21. package/reference/config/errors.md +278 -0
  22. package/reference/config/flags.md +369 -0
  23. package/reference/config/formats.md +314 -0
  24. package/reference/config/index.md +304 -0
  25. package/reference/config/rollout.md +336 -0
  26. package/reference/context.md +6 -49
  27. package/reference/data-migration.md +269 -0
  28. package/reference/datastar/attributes.md +302 -0
  29. package/reference/datastar/events.md +234 -0
  30. package/reference/datastar/index.md +256 -0
  31. package/reference/datastar/signals.md +230 -0
  32. package/reference/datastar/sse.md +295 -0
  33. package/reference/datastar.md +2 -2
  34. package/reference/docs.md +2 -2
  35. package/reference/endpoint/bulk-creation.md +96 -0
  36. package/reference/endpoint/index.md +9 -89
  37. package/reference/endpoint/path-codec.md +12 -24
  38. package/reference/endpoint/route-pattern.md +4 -6
  39. package/reference/endpoint/segment-codec.md +19 -32
  40. package/reference/html.md +313 -9
  41. package/reference/htmx/index.md +4 -52
  42. package/reference/htmx/response-headers.md +240 -0
  43. package/reference/http-model/headers.md +735 -0
  44. package/reference/http-model/index.md +3 -1
  45. package/reference/http-model/model.md +107 -71
  46. package/reference/http-model/schema-codecs.md +522 -0
  47. package/reference/http-model/schema.md +6 -3
  48. package/reference/http-model/server-sent-event.md +341 -0
  49. package/reference/jwt.md +195 -0
  50. package/reference/maybe.md +128 -11
  51. package/reference/media-type.md +2 -2
  52. package/reference/mux.md +254 -0
  53. package/reference/mux.mdx +7 -2
  54. package/reference/openapi.md +3 -3
  55. package/reference/projection.md +654 -0
  56. package/reference/resource-management/resource.md +2 -98
  57. package/reference/resource-management/scope.md +1 -209
  58. package/reference/resource-management/wire.md +4 -50
  59. package/reference/ringbuffer/advanced.mdx +1 -1
  60. package/reference/ringbuffer/index.mdx +3 -3
  61. package/reference/ringbuffer/mpmc.mdx +38 -4
  62. package/reference/ringbuffer/mpsc.mdx +36 -4
  63. package/reference/ringbuffer/spmc.mdx +1 -1
  64. package/reference/ringbuffer/spsc.mdx +87 -15
  65. package/reference/schema/allows.md +0 -96
  66. package/reference/schema/binding.md +2 -2
  67. package/reference/schema/built-in-codecs/avro.md +2 -2
  68. package/reference/schema/built-in-codecs/bson.md +50 -20
  69. package/reference/schema/built-in-codecs/csv.md +2 -2
  70. package/reference/schema/built-in-codecs/index.md +3 -3
  71. package/reference/schema/built-in-codecs/json/index.md +2 -2
  72. package/reference/schema/built-in-codecs/messagepack.md +3 -3
  73. package/reference/schema/built-in-codecs/thrift.md +2 -2
  74. package/reference/schema/built-in-codecs/toon.md +3 -3
  75. package/reference/schema/built-in-codecs/yaml.md +2 -2
  76. package/reference/schema/codec.md +11 -11
  77. package/reference/schema/dynamic-optic.md +48 -3
  78. package/reference/schema/dynamic-schema.md +3 -3
  79. package/reference/schema/index.md +2 -0
  80. package/reference/schema/path-interpolator.md +2 -0
  81. package/reference/schema/reflect-transformer.md +140 -0
  82. package/reference/schema/schema-evolution/as.md +4 -4
  83. package/reference/schema/schema-evolution/into.md +2 -2
  84. package/reference/schema/schema-expr.md +2 -2
  85. package/reference/schema/schema-search.md +263 -0
  86. package/reference/schema/schema.md +10 -2
  87. package/reference/schema/type-class-derivation.md +1 -1
  88. package/reference/smithy.md +502 -3
  89. package/reference/sql/db-codec-deriver.md +3 -3
  90. package/reference/sql/db-codec.md +22 -22
  91. package/reference/sql/db-con.md +4 -4
  92. package/reference/sql/db-connection.md +1 -1
  93. package/reference/sql/db-param.md +1 -1
  94. package/reference/sql/db-result-reader.md +4 -2
  95. package/reference/sql/db-tx.md +46 -14
  96. package/reference/sql/ddl.md +1 -1
  97. package/reference/sql/frag.md +44 -10
  98. package/reference/sql/index.md +7 -7
  99. package/reference/sql/repo.md +15 -15
  100. package/reference/sql/sql-dialect.md +1 -1
  101. package/reference/sql/sql-logger.md +1 -1
  102. package/reference/sql/sql-name-mapper.md +3 -3
  103. package/reference/sql/table-metadata.md +3 -3
  104. package/reference/sql/table.md +10 -10
  105. package/reference/sql/transactor-zio.md +1 -1
  106. package/reference/sql/transactor.md +21 -11
  107. package/reference/sql-zio.md +1 -1
  108. package/reference/streams/core/index.md +32 -0
  109. package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
  110. package/reference/streams/{sink.md → core/sink.md} +331 -353
  111. package/reference/streams/{stream.md → core/stream.md} +919 -209
  112. package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
  113. package/reference/streams/execution-and-compatibility/index.md +35 -0
  114. package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
  115. package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
  116. package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
  117. package/reference/streams/index.md +140 -67
  118. package/reference/streams/primitives/index.md +30 -0
  119. package/reference/streams/primitives/reader.md +1992 -0
  120. package/reference/streams/{writer.md → primitives/writer.md} +254 -98
  121. package/reference/telemetry/common/any-value.md +90 -0
  122. package/reference/telemetry/common/attribute-key.md +87 -0
  123. package/reference/telemetry/common/attributes.md +118 -0
  124. package/reference/telemetry/common/index.md +39 -0
  125. package/reference/telemetry/common/instrumentation-scope.md +24 -0
  126. package/reference/telemetry/common/resource.md +34 -0
  127. package/reference/telemetry/index.md +311 -0
  128. package/reference/telemetry/logging/index.md +197 -0
  129. package/reference/telemetry/logging/log-enrichment.md +72 -0
  130. package/reference/telemetry/logging/log-formatter.md +100 -0
  131. package/reference/telemetry/logging/log-record-processor.md +56 -0
  132. package/reference/telemetry/logging/log-record.md +44 -0
  133. package/reference/telemetry/logging/log-writer.md +64 -0
  134. package/reference/telemetry/logging/logger-provider.md +142 -0
  135. package/reference/telemetry/logging/logger.md +83 -0
  136. package/reference/telemetry/logging/severity.md +62 -0
  137. package/reference/telemetry/metrics/index.md +150 -0
  138. package/reference/telemetry/metrics/instruments.md +183 -0
  139. package/reference/telemetry/metrics/labeled-instruments.md +74 -0
  140. package/reference/telemetry/metrics/meter-provider.md +76 -0
  141. package/reference/telemetry/metrics/meter.md +98 -0
  142. package/reference/telemetry/metrics/metric-data.md +57 -0
  143. package/reference/telemetry/otel/custom-exporter.md +216 -0
  144. package/reference/telemetry/otel/index.md +212 -0
  145. package/reference/telemetry/tracing/index.md +155 -0
  146. package/reference/telemetry/tracing/sampler.md +89 -0
  147. package/reference/telemetry/tracing/span-builder.md +57 -0
  148. package/reference/telemetry/tracing/span-context.md +39 -0
  149. package/reference/telemetry/tracing/span-data.md +32 -0
  150. package/reference/telemetry/tracing/span-kind.md +55 -0
  151. package/reference/telemetry/tracing/span-processor.md +53 -0
  152. package/reference/telemetry/tracing/span-status.md +47 -0
  153. package/reference/telemetry/tracing/span.md +117 -0
  154. package/reference/telemetry/tracing/tracer-provider.md +91 -0
  155. package/reference/telemetry/tracing/tracer.md +52 -0
  156. package/reference/typeid.md +0 -64
  157. package/sidebars.js +150 -12
  158. package/undocumented-report.md +528 -270
  159. package/reference/config.md +0 -158
  160. package/reference/streams/concurrent-operators.md +0 -106
  161. package/reference/streams/reader.md +0 -1284
  162. package/reference/streams/scala-2-compatibility.md +0 -55
  163. package/reference/streams/zero-boxing.md +0 -275
  164. package/reference/telemetry.md +0 -693
@@ -0,0 +1,687 @@
1
+ ---
2
+ id: async-getting-started
3
+ title: "Getting Started with Async"
4
+ description: "Learn to create, compose, and run async effects in Scala with the zero-allocation Async[A] type."
5
+ keywords:
6
+ - "Asynchronous Effects"
7
+ - "Zero Allocation"
8
+ - "Direct Style"
9
+ - "Error Handling"
10
+ - "Async"
11
+ ---
12
+
13
+ import Tabs from '@theme/Tabs';
14
+ import TabItem from '@theme/TabItem';
15
+
16
+ Welcome! This tutorial introduces `Async[A]`, a zero-allocation effect type from ZIO Blocks that unifies ready values, failures, and genuinely suspended computations under a single type and combinator set. If you know basic Scala syntax and have a sense of what an effect type is, you have everything you need to follow along.
17
+
18
+ ## 1. Introduction
19
+
20
+ By the end of this tutorial, you will be able to:
21
+
22
+ - Create ready-value effects with `Async.succeed` and transform them with `Async#map` and `Async#block`.
23
+ - Handle errors with `Async.fail`, `Async#catchAll`, and `Async#either`.
24
+ - Write sequential async code in direct style using `Async.async { … .await … }`.
25
+ - Bridge throw-based code and callback-based APIs with `Async.attempt` and `Async.promise` / `Completer`.
26
+ - Fork background computations with `Async#start` and cancel them with `Async#cancel`.
27
+
28
+ To add the async module to your project, include this dependency in your `build.sbt`:
29
+
30
+ ```scala
31
+ libraryDependencies += "dev.zio" %%% "zio-blocks-async" % "0.0.55"
32
+ ```
33
+
34
+ Then bring the full DSL into scope at the top of each file you use it from:
35
+
36
+ ```scala
37
+ import zio.blocks.async._
38
+ ```
39
+
40
+ We recommend reading from top to bottom — each section builds directly on the one before it.
41
+
42
+ ## 2. Background: What Is `Async[A]`?
43
+
44
+ `Async[A]` was designed to solve a specific problem: code often juggles three different kinds of values — results that are already available, computations that need to wait for I/O or a callback, and failures. Treating these differently in different places creates friction. `Async[A]` is one abstraction that covers all three.
45
+
46
+ The design's most important property is its **happy-path allocation budget: zero**. When you chain `Async.succeed`, `Async#map`, and another `map` call together, every step is a plain function call. No wrapper objects accumulate on the heap. Only a computation that truly suspends — waiting for a callback, a timer, or a thread — leaves a pending object behind. This is the zero-allocation promise: you pay for suspension only when you actually suspend.
47
+
48
+ We will explore `Async[A]` through an order-processing scenario: looking up a user via a callback API, parsing an order ID, checking stock availability in the background, and recovering gracefully from any failure — one concept at a time.
49
+
50
+ ## 3. Ready Values: `Async.succeed`, `map`, and `block`
51
+
52
+ The simplest async computation is one that already has its answer. `Async.succeed(value)` wraps an available value into an `Async[A]` so it can take part in async chains without allocating anything. Once you have an `Async`, you transform it with `Async#map` and drive it to its final result with `Async#block`.
53
+
54
+ Here we wrap the integer `42`, double it with `map`, then extract the result:
55
+
56
+ ```scala
57
+ import zio.blocks.async._
58
+
59
+ val result: Int = Async.succeed(42).map(_ * 2).block
60
+ println(s"Ready mapped: $result")
61
+ ```
62
+
63
+ Expected output:
64
+
65
+ ```text
66
+ Ready mapped: 84
67
+ ```
68
+
69
+ - `Async.succeed(42)` wraps `42` as a ready-value `Async[Int]` with zero allocation.
70
+ - Calling `map` with `_ * 2` applies the function; because the input is already ready, the entire chain is just a function call — no suspension object is created.
71
+ - The `block` call is the eager driver — it polls the async until it completes and returns the final value. It is safe to call on the JVM; on Scala.js it throws if the computation is still suspended.
72
+
73
+ Try changing `42` to a different number and watch the output change accordingly.
74
+
75
+ ## 4. Error Handling: `Async.fail`, `catchAll`, and `either`
76
+
77
+ Not every computation succeeds. `Async.fail(throwable)` creates a failed async that short-circuits all downstream `map` calls without invoking their functions. `Async#catchAll` recovers by applying a function that returns a new `Async`.
78
+
79
+ Here we create a failed computation and recover from it with `catchAll`:
80
+
81
+ ```scala
82
+ import zio.blocks.async._
83
+
84
+ val recovered: String = Async.fail(new Exception("oops"))
85
+ .catchAll(_ => Async.succeed("default"))
86
+ .block
87
+ println(s"Recovered: $recovered")
88
+ ```
89
+
90
+ Expected output:
91
+
92
+ ```text
93
+ Recovered: default
94
+ ```
95
+
96
+ - `Async.fail(new Exception("oops"))` creates a computation that carries the exception as its failure.
97
+ - Calling `catchAll` with a recovery function intercepts the failure; the lambda ignores the specific error and returns a ready-value replacement.
98
+ - The `block` call drives the recovered chain to its result, `"default"`.
99
+
100
+ Sometimes you want to observe both the success and failure branches as plain data rather than handle them immediately. `Async#either` reifies both outcomes as `Either[Throwable, A]` so you can pattern-match on them:
101
+
102
+ ```scala
103
+ import zio.blocks.async._
104
+
105
+ val observed: Either[Throwable, Int] = Async.fail(new Exception("error"))
106
+ .either
107
+ .block
108
+ println(s"Observed: $observed")
109
+ ```
110
+
111
+ Expected output:
112
+
113
+ ```text
114
+ Observed: Left(java.lang.Exception: error)
115
+ ```
116
+
117
+ - Calling `either` wraps the failure in `Left`; a successful result would appear in `Right`, turning the async's outcome into an ordinary Scala value.
118
+ - The `block` call materialises that `Either` so the learner can inspect it.
119
+
120
+ ## 5. Direct Style: `Async.async` and `await`
121
+
122
+ Writing nested `Async#flatMap` chains is precise but becomes hard to read when many steps depend on each other. `Async.async { … }` lets you write that same sequencing in direct style: inside the block, call `Async#await` on any `Async` to extract its value and bind it to a local variable, as if you were writing straight-line code. The compiler rewrites every `await` call into a `flatMap` chain at compile time, so the runtime behaviour is identical.
123
+
124
+ Here we compose a user name and an order ID without a single explicit `flatMap`:
125
+
126
+ ```scala
127
+ import zio.blocks.async._
128
+
129
+ val summary: String = Async.async {
130
+ val user = Async.succeed("Ada").await
131
+ val order = Async.succeed(9001).await
132
+ s"${user}'s order ${order}"
133
+ }.block
134
+ println(s"Summary: $summary")
135
+ ```
136
+
137
+ Expected output:
138
+
139
+ ```text
140
+ Summary: Ada's order 9001
141
+ ```
142
+
143
+ - `Async.async { … }` opens a macro-powered block; the entire expression produces an `Async[String]`.
144
+ - Calling `await` on `Async.succeed("Ada")` extracts `"Ada"` and binds it to `user`; this is not a blocking call — the macro rewrites it into a `flatMap` continuation.
145
+ - Calling `await` on `Async.succeed(9001)` similarly binds the order number to `order`.
146
+ - The final string expression becomes the block's result value.
147
+ - The `block` call drives the whole composed async to completion.
148
+
149
+ :::caution[`await` Is Only Valid Inside `Async.async { … }`]
150
+ The `await` method is enforced by the compiler to be used only inside an `Async.async { … }` block. On Scala 3 the inline expansion fails with:
151
+
152
+ ```
153
+ ".await may only be used directly inside an Async.async { ... } block."
154
+ ```
155
+
156
+ On Scala 2, the `@compileTimeOnly` annotation fires at the same point. Try deleting the `Async.async { … }` wrapper — the compiler will tell you immediately.
157
+ :::
158
+
159
+ ## 6. Bridging Exceptions: `Async.attempt`
160
+
161
+ Scala code often signals failure by throwing exceptions rather than returning error values. `Async.attempt(body)` evaluates a block that may throw and captures any exception as an async failure, turning it into a value that `catchAll` can recover. A block that succeeds produces a ready-value `Async`; one that throws produces a failed `Async`.
162
+
163
+ The example parses a well-formed string and then a malformed one:
164
+
165
+ ```scala
166
+ import zio.blocks.async._
167
+
168
+ val good: Int = Async.attempt("42".toInt).block
169
+ println(s"Parsed: $good")
170
+
171
+ val bad: Either[Throwable, Int] = Async.attempt("oops".toInt).either.block
172
+ println(s"Failed parse: $bad")
173
+ ```
174
+
175
+ Expected output:
176
+
177
+ ```text
178
+ Parsed: 42
179
+ Failed parse: Left(java.lang.NumberFormatException: For input string: "oops")
180
+ ```
181
+
182
+ - `Async.attempt("42".toInt)` evaluates `"42".toInt`; because it succeeds, the result `42` becomes a ready-value `Async[Int]`.
183
+ - `Async.attempt("oops".toInt)` evaluates `"oops".toInt`; the `NumberFormatException` is caught and becomes a failed `Async`.
184
+ - Chaining `either` and `block` drives the failed async and reifies its outcome as `Left(…)`.
185
+
186
+ ## 7. Callback Bridging: `Async.promise` and `Completer`
187
+
188
+ Many real-world APIs — database drivers, network libraries, timers — signal completion by calling a callback rather than returning a value. `Async.promise` lets you lift these APIs into `Async` without rewriting them. It suspends the computation and provides a `Completer[A]` — a thread-safe, one-shot handle — that you can pass to the callback. Calling `completer.succeed(value)` from any thread resolves the async and wakes up any awaiter.
189
+
190
+ The example starts a background thread that completes the promise after a short delay:
191
+
192
+ <Tabs groupId="scala-version" defaultValue="scala2">
193
+ <TabItem value="scala2" label="Scala 2">
194
+
195
+ ```scala
196
+ import zio.blocks.async._
197
+
198
+ val result: String = Async.promise[String] { c =>
199
+ new Thread {
200
+ override def run(): Unit = {
201
+ Thread.sleep(10)
202
+ c.succeed("hello from callback")
203
+ }
204
+ }.start()
205
+ }.block
206
+ println(s"Promise resolved: $result")
207
+ ```
208
+
209
+ </TabItem>
210
+ <TabItem value="scala3" label="Scala 3">
211
+
212
+ ```scala
213
+ import zio.blocks.async._
214
+
215
+ val result: String = Async.promise[String] {
216
+ val c = summon[Completer[String]]
217
+ new Thread {
218
+ override def run(): Unit = {
219
+ Thread.sleep(10)
220
+ c.succeed("hello from callback")
221
+ }
222
+ }.start()
223
+ }.block
224
+ println(s"Promise resolved: $result")
225
+ ```
226
+
227
+ </TabItem>
228
+ </Tabs>
229
+
230
+ Expected output:
231
+
232
+ ```text
233
+ Promise resolved: hello from callback
234
+ ```
235
+
236
+ - `Async.promise[String] { … }` opens the promise body; on Scala 2 the `Completer[String]` arrives as an explicit parameter `c`; on Scala 3 it arrives as a context function argument retrieved with `summon[Completer[String]]`.
237
+ - We capture the completer in `c` so it can be referenced from the Thread's `run()` method — implicits and givens do not propagate across thread boundaries, so we capture explicitly.
238
+ - Calling `c.succeed("hello from callback")` completes the promise; the first call wins and subsequent calls are no-ops.
239
+ - The `block` call waits until the completer fires and returns the resolved value.
240
+
241
+ ## 8. Forking and Cancellation: `start` and `Async.Running`
242
+
243
+ By default, async chains run eagerly on the calling thread until the first suspension or completion. To drive a computation on a **background worker** (a separate JVM thread or a Scala.js microtask queue entry) instead, call `Async#start` on any `Async`. This returns an `Async.Running[A]` handle — itself an `Async[A]` — representing the in-flight computation. You can join it by calling `block` on the handle, or stop it early with `cancel`.
244
+
245
+ The first block forks a computation and joins it:
246
+
247
+ ```scala
248
+ import zio.blocks.async._
249
+
250
+ val running: Async.Running[Int] = Async.succeed(42)
251
+ .map { x => println(s"Running in background: $x"); x * 2 }
252
+ .start
253
+ val joined: Int = running.block
254
+ println(s"Joined: $joined")
255
+ ```
256
+
257
+ Expected output:
258
+
259
+ ```text
260
+ Running in background: 42
261
+ Joined: 84
262
+ ```
263
+
264
+ - Calling `start` forks the entire chain — `Async.succeed(42)` plus the `map` — onto a background worker; the calling thread continues immediately.
265
+ - `running.block` blocks the calling thread until the background computation finishes and returns the result `84`.
266
+ - The background computation prints its message before returning the value, so that line appears first.
267
+
268
+ The companion method `Async.start` forks a plain expression. The following block demonstrates cancellation:
269
+
270
+ ```scala
271
+ import zio.blocks.async._
272
+
273
+ val running2: Async.Running[Int] = Async.start { Thread.sleep(100); 99 }
274
+ running2.cancel()
275
+ println("Cancelled running2")
276
+ ```
277
+
278
+ Expected output:
279
+
280
+ ```text
281
+ Cancelled running2
282
+ ```
283
+
284
+ - `Async.start { Thread.sleep(100); 99 }` forks the block onto a background worker; the call returns the `Running` handle immediately.
285
+ - Calling `cancel` stops the driver loop; if cancellation linearises before the computation finishes, the result is never published to any awaiter. The call is idempotent.
286
+
287
+ ## 9. Putting It Together
288
+
289
+ Let's combine all six concepts into a single order-processing pipeline. The program bridges a callback-based user-lookup API with `Async.promise`, safely parses an order ID with `Async.attempt`, runs a stock check in the background with `start`, sequences everything in direct style inside `Async.async`, and recovers from any failure with `catchAll`:
290
+
291
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CompleteExample.scala"
292
+ package zio.blocks.async.gettingstarted
293
+
294
+ import zio.blocks.async._
295
+
296
+ /**
297
+ * Complete Example: Order Processing Pipeline
298
+ *
299
+ * Combines all core `Async` concepts in a single order-processing scenario:
300
+ * - `Async.promise` + `Completer` to bridge a callback-based user-lookup API
301
+ * - `Async.attempt` to safely parse a potentially malformed order ID
302
+ * - `.start` + `Async.Running` to check stock availability in the background
303
+ * - `Async.async { .await }` for direct-style sequential composition
304
+ * - `.catchAll` to recover from any failure in the pipeline
305
+ *
306
+ * Run with:
307
+ * {{{
308
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
309
+ * }}}
310
+ */
311
+ object CompleteExample {
312
+ def main(args: Array[String]): Unit = {
313
+
314
+ // Step 1: Bridge a callback-based user-lookup API with promise + Completer
315
+ val userLookup: Async[String] = Async.promise[String] {
316
+ val c = summon[Completer[String]]
317
+ new Thread {
318
+ override def run(): Unit = {
319
+ Thread.sleep(10)
320
+ c.succeed("Ada")
321
+ }
322
+ }.start()
323
+ }
324
+
325
+ // Step 2: Parse an order ID that might be malformed
326
+ val orderId: Async[Int] = Async.attempt("9001".toInt)
327
+
328
+ // Step 3: Start a stock-availability check in the background
329
+ val stockCheck: Async.Running[Boolean] = Async.succeed(true).map { v => Thread.sleep(5); v }.start
330
+
331
+ // Step 4: Compose all steps in direct style and recover from any failure
332
+ val report: String = Async.async {
333
+ val user = userLookup.await
334
+ val id = orderId.await
335
+ val inStock = stockCheck.await
336
+ s"Order $id for $user: ${if (inStock) "in stock" else "out of stock"}"
337
+ }.catchAll { err =>
338
+ Async.succeed(s"Order failed: ${err.getMessage}")
339
+ }.block
340
+
341
+ println(report)
342
+ }
343
+ }
344
+ ```
345
+
346
+ ## 10. Running the Examples
347
+
348
+ Clone the repository and move into its root directory:
349
+
350
+ ```bash
351
+ git clone https://github.com/zio/zio-blocks.git
352
+ cd zio-blocks
353
+ ```
354
+
355
+ Each concept's standalone example is shown below. Expand a section to see the source and the command to run it.
356
+
357
+ <details>
358
+ <summary><strong>Concept 1: Ready Values</strong></summary>
359
+
360
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ReadyValuesExample.scala" showLineNumbers
361
+ package zio.blocks.async.gettingstarted
362
+
363
+ import zio.blocks.async._
364
+
365
+ /**
366
+ * Section 1: Ready Values
367
+ *
368
+ * Demonstrates how to wrap an already-available value into an `Async` with
369
+ * `Async.succeed`, transform it using `.map`, and drive it to a result with
370
+ * `.block`.
371
+ *
372
+ * Run with:
373
+ * {{{
374
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.ReadyValuesExample"
375
+ * }}}
376
+ */
377
+ object ReadyValuesExample {
378
+ def main(args: Array[String]): Unit = {
379
+ val result: Int = Async.succeed(42).map(_ * 2).block
380
+ println(s"Ready mapped: $result")
381
+ }
382
+ }
383
+ ```
384
+
385
+ Run it with:
386
+
387
+ ```bash
388
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.ReadyValuesExample"
389
+ ```
390
+
391
+ </details>
392
+
393
+ <details>
394
+ <summary><strong>Concept 2: Error Handling</strong></summary>
395
+
396
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ErrorHandlingExample.scala" showLineNumbers
397
+ package zio.blocks.async.gettingstarted
398
+
399
+ import zio.blocks.async._
400
+
401
+ /**
402
+ * Section 2: Error Handling
403
+ *
404
+ * Demonstrates how to create failed async computations with `Async.fail`,
405
+ * recover from failures using `.catchAll`, and reify both outcomes as
406
+ * `Either[Throwable, A]` using `.either`.
407
+ *
408
+ * Run with:
409
+ * {{{
410
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.ErrorHandlingExample"
411
+ * }}}
412
+ */
413
+ object ErrorHandlingExample {
414
+ def main(args: Array[String]): Unit = {
415
+ val recovered: String = Async
416
+ .fail(new Exception("oops"))
417
+ .catchAll(_ => Async.succeed("default"))
418
+ .block
419
+ println(s"Recovered: $recovered")
420
+
421
+ val observed: Either[Throwable, Int] = Async.fail(new Exception("error")).either.block
422
+ println(s"Observed: $observed")
423
+ }
424
+ }
425
+ ```
426
+
427
+ Run it with:
428
+
429
+ ```bash
430
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.ErrorHandlingExample"
431
+ ```
432
+
433
+ </details>
434
+
435
+ <details>
436
+ <summary><strong>Concept 3: Direct Style</strong></summary>
437
+
438
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/DirectStyleExample.scala" showLineNumbers
439
+ package zio.blocks.async.gettingstarted
440
+
441
+ import zio.blocks.async._
442
+
443
+ /**
444
+ * Section 3: Direct Style
445
+ *
446
+ * Demonstrates how to write sequential async code in direct style using
447
+ * `Async.async { ... }` and `.await`. The compiler rewrites `.await` calls into
448
+ * `flatMap` chains, so the code reads like straight-line imperative code
449
+ * without explicit callback nesting.
450
+ *
451
+ * Run with:
452
+ * {{{
453
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.DirectStyleExample"
454
+ * }}}
455
+ */
456
+ object DirectStyleExample {
457
+ def main(args: Array[String]): Unit = {
458
+ val summary: String = Async.async {
459
+ val user = Async.succeed("Ada").await
460
+ val order = Async.succeed(9001).await
461
+ s"${user}'s order ${order}"
462
+ }.block
463
+ println(s"Summary: $summary")
464
+ }
465
+ }
466
+ ```
467
+
468
+ Run it with:
469
+
470
+ ```bash
471
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.DirectStyleExample"
472
+ ```
473
+
474
+ </details>
475
+
476
+ <details>
477
+ <summary><strong>Concept 4: Bridging Exceptions</strong></summary>
478
+
479
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/AttemptExample.scala" showLineNumbers
480
+ package zio.blocks.async.gettingstarted
481
+
482
+ import zio.blocks.async._
483
+
484
+ /**
485
+ * Section 4: Bridging Exceptions
486
+ *
487
+ * Demonstrates how `Async.attempt` evaluates a block that may throw and
488
+ * captures any exception as an async failure rather than propagating it as a
489
+ * JVM exception. A successful evaluation produces a ready-value `Async`; a
490
+ * thrown exception produces a failed `Async` recoverable with `.catchAll`.
491
+ *
492
+ * Run with:
493
+ * {{{
494
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.AttemptExample"
495
+ * }}}
496
+ */
497
+ object AttemptExample {
498
+ def main(args: Array[String]): Unit = {
499
+ val good: Int = Async.attempt("42".toInt).block
500
+ println(s"Parsed: $good")
501
+
502
+ val bad: Either[Throwable, Int] = Async.attempt("oops".toInt).either.block
503
+ println(s"Failed parse: $bad")
504
+ }
505
+ }
506
+ ```
507
+
508
+ Run it with:
509
+
510
+ ```bash
511
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.AttemptExample"
512
+ ```
513
+
514
+ </details>
515
+
516
+ <details>
517
+ <summary><strong>Concept 5: Callback Bridging</strong></summary>
518
+
519
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CallbackBridgeExample.scala" showLineNumbers
520
+ package zio.blocks.async.gettingstarted
521
+
522
+ import zio.blocks.async._
523
+
524
+ /**
525
+ * Section 5: Callback Bridging
526
+ *
527
+ * Demonstrates how to lift a callback-based API into an `Async` using
528
+ * `Async.promise` and a `Completer`. The body of `Async.promise` is a Scala 3
529
+ * context function: `summon[Completer[A]]` retrieves the completer, which can
530
+ * then be captured and called from any thread.
531
+ *
532
+ * Run with:
533
+ * {{{
534
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.CallbackBridgeExample"
535
+ * }}}
536
+ */
537
+ object CallbackBridgeExample {
538
+ def main(args: Array[String]): Unit = {
539
+ val result: String = Async
540
+ .promise[String] {
541
+ val c = summon[Completer[String]]
542
+ new Thread {
543
+ override def run(): Unit = {
544
+ Thread.sleep(10)
545
+ c.succeed("hello from callback")
546
+ }
547
+ }.start()
548
+ }
549
+ .block
550
+ println(s"Promise resolved: $result")
551
+ }
552
+ }
553
+ ```
554
+
555
+ Run it with:
556
+
557
+ ```bash
558
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.CallbackBridgeExample"
559
+ ```
560
+
561
+ </details>
562
+
563
+ <details>
564
+ <summary><strong>Concept 6: Forking and Cancellation</strong></summary>
565
+
566
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/ForkingExample.scala" showLineNumbers
567
+ package zio.blocks.async.gettingstarted
568
+
569
+ import zio.blocks.async._
570
+
571
+ /**
572
+ * Section 6: Forking and Cancellation
573
+ *
574
+ * Demonstrates how to run a computation on a background worker with `.start`,
575
+ * which returns an `Async.Running[A]` handle. The handle can be joined with
576
+ * `.block` (blocks the caller until the computation completes) or cancelled
577
+ * with `.cancel()` (stops the driver loop before the result is published).
578
+ *
579
+ * Run with:
580
+ * {{{
581
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.ForkingExample"
582
+ * }}}
583
+ */
584
+ object ForkingExample {
585
+ def main(args: Array[String]): Unit = {
586
+ // Fork and join
587
+ val running: Async.Running[Int] = Async.succeed(42).map { x => println(s"Running in background: $x"); x * 2 }.start
588
+ val joined: Int = running.block
589
+ println(s"Joined: $joined")
590
+
591
+ // Fork and cancel
592
+ val running2: Async.Running[Int] = Async.start { Thread.sleep(100); 99 }
593
+ running2.cancel()
594
+ println("Cancelled running2")
595
+ }
596
+ }
597
+ ```
598
+
599
+ Run it with:
600
+
601
+ ```bash
602
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.ForkingExample"
603
+ ```
604
+
605
+ </details>
606
+
607
+ <details>
608
+ <summary><strong>Complete Example: Order Processing Pipeline</strong></summary>
609
+
610
+ ```scala title="async-examples/src/main/scala/zio/blocks/async/gettingstarted/CompleteExample.scala" showLineNumbers
611
+ package zio.blocks.async.gettingstarted
612
+
613
+ import zio.blocks.async._
614
+
615
+ /**
616
+ * Complete Example: Order Processing Pipeline
617
+ *
618
+ * Combines all core `Async` concepts in a single order-processing scenario:
619
+ * - `Async.promise` + `Completer` to bridge a callback-based user-lookup API
620
+ * - `Async.attempt` to safely parse a potentially malformed order ID
621
+ * - `.start` + `Async.Running` to check stock availability in the background
622
+ * - `Async.async { .await }` for direct-style sequential composition
623
+ * - `.catchAll` to recover from any failure in the pipeline
624
+ *
625
+ * Run with:
626
+ * {{{
627
+ * sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
628
+ * }}}
629
+ */
630
+ object CompleteExample {
631
+ def main(args: Array[String]): Unit = {
632
+
633
+ // Step 1: Bridge a callback-based user-lookup API with promise + Completer
634
+ val userLookup: Async[String] = Async.promise[String] {
635
+ val c = summon[Completer[String]]
636
+ new Thread {
637
+ override def run(): Unit = {
638
+ Thread.sleep(10)
639
+ c.succeed("Ada")
640
+ }
641
+ }.start()
642
+ }
643
+
644
+ // Step 2: Parse an order ID that might be malformed
645
+ val orderId: Async[Int] = Async.attempt("9001".toInt)
646
+
647
+ // Step 3: Start a stock-availability check in the background
648
+ val stockCheck: Async.Running[Boolean] = Async.succeed(true).map { v => Thread.sleep(5); v }.start
649
+
650
+ // Step 4: Compose all steps in direct style and recover from any failure
651
+ val report: String = Async.async {
652
+ val user = userLookup.await
653
+ val id = orderId.await
654
+ val inStock = stockCheck.await
655
+ s"Order $id for $user: ${if (inStock) "in stock" else "out of stock"}"
656
+ }.catchAll { err =>
657
+ Async.succeed(s"Order failed: ${err.getMessage}")
658
+ }.block
659
+
660
+ println(report)
661
+ }
662
+ }
663
+ ```
664
+
665
+ Run it with:
666
+
667
+ ```bash
668
+ sbt "async-examples/runMain zio.blocks.async.gettingstarted.CompleteExample"
669
+ ```
670
+
671
+ </details>
672
+
673
+ ## 11. What You've Learned
674
+
675
+ By completing this tutorial, you can now:
676
+
677
+ - Create ready-value effects with `Async.succeed`, transform them with `map`, and drive them to a result with `block`.
678
+ - Handle failures with `Async.fail`, recover them with `catchAll`, and observe both outcomes as data with `either`.
679
+ - Write sequential async pipelines in direct style using `Async.async { … .await … }` — the compiler threads the `flatMap` calls for you.
680
+ - Lift throw-based Scala code safely into the async error channel with `Async.attempt`.
681
+ - Bridge a callback-based API with `Async.promise` and `Completer`, fork background work with `start`, and hold a cancellable `Async.Running` handle.
682
+
683
+ ## 12. Where to Go Next
684
+
685
+ The [Async reference page](../reference/async.md) documents every method and combinator with full signatures — it is the natural next stop once you are comfortable with the basics in this tutorial.
686
+
687
+ If your application manages resources that need deterministic cleanup — database connections, file handles, or connection pools — read the [Compile-Time Resource Safety with Scope](./compile-time-resource-safety-with-scope.md) tutorial, which shows how to tie resource lifetimes to lexical scopes and compose them without try/finally boilerplate.
@@ -994,3 +994,9 @@ You now understand Scope's core concepts:
994
994
  - **Thread ownership** — JVM enforcement of structured concurrency.
995
995
 
996
996
  For complete API documentation, see the [Scope Reference](../reference/resource-management/scope.md).
997
+
998
+ ## See Also
999
+
1000
+ - [Telemetry Reference](../reference/telemetry/index.md) — `TracerProvider`, `LoggerProvider`, and `MeterProvider` are `AutoCloseable`, so the ownership rules here carry over to their lifetimes
1001
+ - [Telemetry Guide](./telemetry-guide.md) — Provider startup and shutdown ordering, done with plain `AutoCloseable` shutdown rather than `Scope`
1002
+ - [Async Reference](../reference/async.md) — `Async.Running` extends `AutoCloseable` and integrates with `scala.util.Using` for scoped cancellation; the same resource-ownership mental model used by Scope applies to in-flight async computations