@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.
- package/adr/2026-07-18-data-migration.md +123 -0
- package/guides/async-getting-started.md +687 -0
- package/guides/compile-time-resource-safety-with-scope.md +6 -0
- package/guides/getting-started-with-mux.md +0 -112
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +1 -1
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +395 -1
- package/guides/sql-checked-interpolation.md +173 -0
- package/guides/sql-transactions.md +286 -0
- package/guides/telemetry-guide.md +131 -70
- package/guides/zio-schema-migration.md +6 -6
- package/index.md +200 -583
- package/package.json +1 -1
- package/reference/async.md +1379 -531
- package/reference/chunk.md +3 -3
- package/reference/codegen/index.md +1 -1
- package/reference/combinators.md +4 -4
- package/reference/config/config-decoder.md +460 -0
- package/reference/config/config-source.md +489 -0
- package/reference/config/errors.md +278 -0
- package/reference/config/flags.md +369 -0
- package/reference/config/formats.md +314 -0
- package/reference/config/index.md +304 -0
- package/reference/config/rollout.md +336 -0
- package/reference/context.md +6 -49
- package/reference/data-migration.md +269 -0
- package/reference/datastar/attributes.md +302 -0
- package/reference/datastar/events.md +234 -0
- package/reference/datastar/index.md +256 -0
- package/reference/datastar/signals.md +230 -0
- package/reference/datastar/sse.md +295 -0
- package/reference/datastar.md +2 -2
- package/reference/docs.md +2 -2
- package/reference/endpoint/bulk-creation.md +96 -0
- package/reference/endpoint/index.md +9 -89
- package/reference/endpoint/path-codec.md +12 -24
- package/reference/endpoint/route-pattern.md +4 -6
- package/reference/endpoint/segment-codec.md +19 -32
- package/reference/html.md +313 -9
- package/reference/htmx/index.md +4 -52
- package/reference/htmx/response-headers.md +240 -0
- package/reference/http-model/headers.md +735 -0
- package/reference/http-model/index.md +3 -1
- package/reference/http-model/model.md +107 -71
- package/reference/http-model/schema-codecs.md +522 -0
- package/reference/http-model/schema.md +6 -3
- package/reference/http-model/server-sent-event.md +341 -0
- package/reference/jwt.md +195 -0
- package/reference/maybe.md +128 -11
- package/reference/media-type.md +2 -2
- package/reference/mux.md +254 -0
- package/reference/mux.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/resource.md +2 -98
- package/reference/resource-management/scope.md +1 -209
- package/reference/resource-management/wire.md +4 -50
- package/reference/ringbuffer/advanced.mdx +1 -1
- package/reference/ringbuffer/index.mdx +3 -3
- package/reference/ringbuffer/mpmc.mdx +38 -4
- package/reference/ringbuffer/mpsc.mdx +36 -4
- package/reference/ringbuffer/spmc.mdx +1 -1
- package/reference/ringbuffer/spsc.mdx +87 -15
- package/reference/schema/allows.md +0 -96
- package/reference/schema/binding.md +2 -2
- package/reference/schema/built-in-codecs/avro.md +2 -2
- package/reference/schema/built-in-codecs/bson.md +50 -20
- package/reference/schema/built-in-codecs/csv.md +2 -2
- package/reference/schema/built-in-codecs/index.md +3 -3
- package/reference/schema/built-in-codecs/json/index.md +2 -2
- package/reference/schema/built-in-codecs/messagepack.md +3 -3
- package/reference/schema/built-in-codecs/thrift.md +2 -2
- package/reference/schema/built-in-codecs/toon.md +3 -3
- package/reference/schema/built-in-codecs/yaml.md +2 -2
- package/reference/schema/codec.md +11 -11
- package/reference/schema/dynamic-optic.md +48 -3
- package/reference/schema/dynamic-schema.md +3 -3
- package/reference/schema/index.md +2 -0
- package/reference/schema/path-interpolator.md +2 -0
- package/reference/schema/reflect-transformer.md +140 -0
- package/reference/schema/schema-evolution/as.md +4 -4
- package/reference/schema/schema-evolution/into.md +2 -2
- package/reference/schema/schema-expr.md +2 -2
- package/reference/schema/schema-search.md +263 -0
- package/reference/schema/schema.md +10 -2
- package/reference/schema/type-class-derivation.md +1 -1
- package/reference/smithy.md +502 -3
- package/reference/sql/db-codec-deriver.md +3 -3
- package/reference/sql/db-codec.md +22 -22
- package/reference/sql/db-con.md +4 -4
- package/reference/sql/db-connection.md +1 -1
- package/reference/sql/db-param.md +1 -1
- package/reference/sql/db-result-reader.md +4 -2
- package/reference/sql/db-tx.md +46 -14
- package/reference/sql/ddl.md +1 -1
- package/reference/sql/frag.md +44 -10
- package/reference/sql/index.md +7 -7
- package/reference/sql/repo.md +15 -15
- package/reference/sql/sql-dialect.md +1 -1
- package/reference/sql/sql-logger.md +1 -1
- package/reference/sql/sql-name-mapper.md +3 -3
- package/reference/sql/table-metadata.md +3 -3
- package/reference/sql/table.md +10 -10
- package/reference/sql/transactor-zio.md +1 -1
- package/reference/sql/transactor.md +21 -11
- package/reference/sql-zio.md +1 -1
- package/reference/streams/core/index.md +32 -0
- package/reference/streams/{pipeline.md → core/pipeline.md} +210 -74
- package/reference/streams/{sink.md → core/sink.md} +331 -353
- package/reference/streams/{stream.md → core/stream.md} +919 -209
- package/reference/streams/execution-and-compatibility/async-execution.md +822 -0
- package/reference/streams/execution-and-compatibility/index.md +35 -0
- package/reference/streams/execution-and-compatibility/platform-differences.md +297 -0
- package/reference/streams/execution-and-compatibility/scala-2-compatibility.md +88 -0
- package/reference/streams/execution-and-compatibility/zero-boxing.md +393 -0
- package/reference/streams/index.md +140 -67
- package/reference/streams/primitives/index.md +30 -0
- package/reference/streams/primitives/reader.md +1992 -0
- package/reference/streams/{writer.md → primitives/writer.md} +254 -98
- package/reference/telemetry/common/any-value.md +90 -0
- package/reference/telemetry/common/attribute-key.md +87 -0
- package/reference/telemetry/common/attributes.md +118 -0
- package/reference/telemetry/common/index.md +39 -0
- package/reference/telemetry/common/instrumentation-scope.md +24 -0
- package/reference/telemetry/common/resource.md +34 -0
- package/reference/telemetry/index.md +311 -0
- package/reference/telemetry/logging/index.md +197 -0
- package/reference/telemetry/logging/log-enrichment.md +72 -0
- package/reference/telemetry/logging/log-formatter.md +100 -0
- package/reference/telemetry/logging/log-record-processor.md +56 -0
- package/reference/telemetry/logging/log-record.md +44 -0
- package/reference/telemetry/logging/log-writer.md +64 -0
- package/reference/telemetry/logging/logger-provider.md +142 -0
- package/reference/telemetry/logging/logger.md +83 -0
- package/reference/telemetry/logging/severity.md +62 -0
- package/reference/telemetry/metrics/index.md +150 -0
- package/reference/telemetry/metrics/instruments.md +183 -0
- package/reference/telemetry/metrics/labeled-instruments.md +74 -0
- package/reference/telemetry/metrics/meter-provider.md +76 -0
- package/reference/telemetry/metrics/meter.md +98 -0
- package/reference/telemetry/metrics/metric-data.md +57 -0
- package/reference/telemetry/otel/custom-exporter.md +216 -0
- package/reference/telemetry/otel/index.md +212 -0
- package/reference/telemetry/tracing/index.md +155 -0
- package/reference/telemetry/tracing/sampler.md +89 -0
- package/reference/telemetry/tracing/span-builder.md +57 -0
- package/reference/telemetry/tracing/span-context.md +39 -0
- package/reference/telemetry/tracing/span-data.md +32 -0
- package/reference/telemetry/tracing/span-kind.md +55 -0
- package/reference/telemetry/tracing/span-processor.md +53 -0
- package/reference/telemetry/tracing/span-status.md +47 -0
- package/reference/telemetry/tracing/span.md +117 -0
- package/reference/telemetry/tracing/tracer-provider.md +91 -0
- package/reference/telemetry/tracing/tracer.md +52 -0
- package/reference/typeid.md +0 -64
- package/sidebars.js +150 -12
- package/undocumented-report.md +528 -270
- package/reference/config.md +0 -158
- package/reference/streams/concurrent-operators.md +0 -106
- package/reference/streams/reader.md +0 -1284
- package/reference/streams/scala-2-compatibility.md +0 -55
- package/reference/streams/zero-boxing.md +0 -275
- package/reference/telemetry.md +0 -693
package/reference/maybe.md
CHANGED
|
@@ -3,44 +3,148 @@ id: maybe
|
|
|
3
3
|
title: "Maybe"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`Maybe[A]` is a **low-allocation alternative to `Option[A]`** that uses `
|
|
6
|
+
`Maybe[A]` is a **low-allocation alternative to `Option[A]`** that uses a top-level `Absent` sentinel object to represent the absence of a value. On Scala 3, it is an opaque type alias for `A | Absent.type | Present[A]`, where `Present[A]` is a public wrapper allocated whenever a present value would otherwise be indistinguishable from absence — nested `Maybe`s (`Maybe.present(Maybe.absent)` → `Present(Absent)`) and `null` values (`Maybe.present(null)` → `Present(null)`). On Scala 2.13, it is a sealed trait (`Present[A]` | `Absent`). Core types: `Maybe[A]`, `Present[A]`.
|
|
7
7
|
|
|
8
8
|
Here's the type definition and basic construction:
|
|
9
9
|
|
|
10
10
|
```scala
|
|
11
|
-
|
|
11
|
+
// Scala 3
|
|
12
|
+
final class Present[+A](val value: A) // manual companion: apply + unapply that matches both raw values and wrappers
|
|
13
|
+
object Absent
|
|
14
|
+
opaque type Maybe[+A] = A | Absent.type | Present[A]
|
|
12
15
|
|
|
13
16
|
val present: Maybe[Int] = Maybe.present(42)
|
|
14
17
|
val absent: Maybe[Int] = Maybe.absent
|
|
15
18
|
```
|
|
16
19
|
|
|
20
|
+
## Allocation Profile
|
|
21
|
+
|
|
22
|
+
The new encoding minimizes allocation by using the raw value when possible:
|
|
23
|
+
|
|
24
|
+
| Case | Example | Allocations |
|
|
25
|
+
|------|---------|-------------|
|
|
26
|
+
| Flat present | `Maybe.present(42)` | **0** (raw `42`) |
|
|
27
|
+
| Flat apply | `Maybe(42)` | **0** (raw `42`) |
|
|
28
|
+
| Flat fromOption | `Maybe.fromOption(Some(42))` | **0** (raw `42`) |
|
|
29
|
+
| Absent | `Maybe.absent` | **0** (`Absent` singleton) |
|
|
30
|
+
| Present-of-absent | `Maybe.present(Maybe.absent)` | **1** (`Present(Absent)`) |
|
|
31
|
+
| Present null | `Maybe.fromOption(Some(null))` | **1** (`Present(null)`) |
|
|
32
|
+
|
|
33
|
+
The `Present[A]` wrapper is allocated **only** for present-of-absent cases: wrapping a nested `Maybe` that is itself absent (`Present(Absent)`) and wrapping a `null` value (`Present(null)`). All other flat cases store the value raw with zero allocation overhead.
|
|
34
|
+
|
|
35
|
+
### Nesting Depth
|
|
36
|
+
|
|
37
|
+
Nested `Maybe` values reduce wrapper depth by 1 compared to `Option`:
|
|
38
|
+
|
|
39
|
+
```scala
|
|
40
|
+
import zio.blocks.maybe._
|
|
41
|
+
|
|
42
|
+
// Option: Some(None) = 2 wrappers
|
|
43
|
+
val optNested: Option[Option[Int]] = Some(None)
|
|
44
|
+
|
|
45
|
+
// Maybe: Present(Absent) = 1 wrapper (Present) + Absent singleton
|
|
46
|
+
val maybeNested: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
|
|
47
|
+
// maybeNested matches Present(Absent), not Absent
|
|
48
|
+
|
|
49
|
+
// Flattening removes the Present wrapper
|
|
50
|
+
val flat: Maybe[Int] = maybeNested.flatten
|
|
51
|
+
// flat is absent (Absent)
|
|
52
|
+
```
|
|
53
|
+
|
|
17
54
|
## Motivation
|
|
18
55
|
|
|
19
56
|
When working with optional values, you face a choice: `Option[A]` provides type safety and functional composition but allocates a wrapper object for every value. `Maybe[A]` provides an alternative with different trade-offs depending on your Scala version.
|
|
20
57
|
|
|
21
|
-
**On Scala 3:** `Maybe[A]` eliminates allocation overhead by leveraging union types and
|
|
58
|
+
**On Scala 3:** `Maybe[A]` eliminates allocation overhead by leveraging union types and a top-level `Absent` sentinel. The type is an opaque alias for `A | Absent.type | Present[A]`, where `Present[A]` is a public wrapper allocated whenever a present value would otherwise be indistinguishable from absence. Flat values (non-nested, non-null) are stored raw with zero allocation; only present-of-absent values allocate a `Present` wrapper (`Maybe.present(Maybe.absent)` → `Present(Absent)`, `Maybe.present(null)` → `Present(null)`). This gives you a dedicated API (`map`, `flatMap`, `filter`, etc.) with minimal runtime overhead. `case Absent` is a stable-identifier pattern that works directly.
|
|
22
59
|
|
|
23
60
|
**On Scala 2.13:** `Maybe[A]` is implemented as a sealed trait (`Present[A]` | `Absent`). Present values allocate a wrapper, so the allocation savings versus `Option` are less pronounced. However, the unified API and interoperability benefits still apply.
|
|
24
61
|
|
|
25
62
|
### Why Maybe over Option?
|
|
26
63
|
|
|
27
|
-
- **Zero allocation**:
|
|
64
|
+
- **Zero allocation (flat case)**: Non-nested `Maybe` values are either the raw value itself or the `Absent` singleton—no wrapper objects
|
|
65
|
+
- **Sound nesting**: Nested `Maybe[Maybe[A]]` is now sound via `Present[A]`, without requiring a compile-time guard
|
|
28
66
|
- **Familiar API**: All your favorite `Option` combinators (`map`, `flatMap`, `fold`, etc.)
|
|
29
67
|
- **Type safety**: The opaque type prevents accidentally mixing nullable and non-nullable values
|
|
30
68
|
- **Interoperable**: Seamless conversion to/from `Option` with `toOption` and `Maybe#fromOption`
|
|
31
69
|
|
|
70
|
+
## Cross-Version Parity
|
|
71
|
+
|
|
72
|
+
The API surface is consistent across Scala 2.13 and Scala 3, but the underlying encoding differs:
|
|
73
|
+
|
|
74
|
+
| Aspect | Scala 3 | Scala 2.13 |
|
|
75
|
+
|--------|---------|------------|
|
|
76
|
+
| Encoding | `opaque type Maybe[+A] = A \| Absent.type \| Present[A]` | Sealed trait `Present[A]` \| `Absent` |
|
|
77
|
+
| Flat allocation | **0** (raw value or `Absent` singleton) | **1** (wrapper object) |
|
|
78
|
+
| Nested allocation | **1** (`Present(Absent)` only) | **1** (wrapper object) |
|
|
79
|
+
| `Present` type | Public `final class Present[+A]` with manual companion (`apply`/`unapply`) | `MaybeValue.Present[A]` (sealed) |
|
|
80
|
+
| Nesting guard | **Removed** (nesting now sound via `Present`) | N/A (never existed) |
|
|
81
|
+
|
|
82
|
+
### Behavior Differences
|
|
83
|
+
|
|
84
|
+
- **`Maybe.present(null)`**: On Scala 3, produces `Present(null)` (present-of-absent). On Scala 2.13, produces `MaybeValue.Present(null)` (also present-of-absent). Both are distinguishable from `Maybe.absent`.
|
|
85
|
+
- **`Maybe.fromOption(Some(null))`**: Routes through `present`, so same behavior as above.
|
|
86
|
+
- **Pattern matching**: On Scala 3, `Present(v)` matches both present shapes (a raw value and a `Present(...)` wrapper); absent matches `case Absent` (stable-identifier pattern, no `case _` required inside the package; external two-case matches also compile without exhaustivity warning). On Scala 2.13, `MaybeValue.Present(v)` / `MaybeValue.Absent` are the compiler-checked native patterns.
|
|
87
|
+
|
|
88
|
+
## Pattern Matching
|
|
89
|
+
|
|
90
|
+
On Scala 3, a `Maybe[A]` value can take three runtime shapes. The `Present` companion's `unapply` collapses the two present shapes into one pattern; absent is now a real top-level singleton object:
|
|
91
|
+
|
|
92
|
+
| Shape | Pattern | Meaning |
|
|
93
|
+
|-------|---------|---------|
|
|
94
|
+
| `Absent` object | `case Absent` | absent (stable-identifier pattern) |
|
|
95
|
+
| `Present(v)` wrapper | `case Present(v)` | present-of-absent (a nested `Maybe`) |
|
|
96
|
+
| raw `v` | `case Present(v)` | present, zero allocation |
|
|
97
|
+
|
|
98
|
+
(Note: the `Present` companion's `unapply` collapses both present shapes — one `Some` allocation per present match.)
|
|
99
|
+
|
|
100
|
+
```scala
|
|
101
|
+
import zio.blocks.maybe._
|
|
102
|
+
|
|
103
|
+
val maybe: Maybe[Int] = Maybe.present(42)
|
|
104
|
+
|
|
105
|
+
val description: String = maybe match {
|
|
106
|
+
case Present(v) => s"present ($v)"
|
|
107
|
+
case Absent => "absent"
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`case Absent` is a stable-identifier pattern that works because `Absent` is a plain top-level object (not a case object). The two-case match `case Present(v); case Absent` compiles without exhaustivity warning even from an external package — verified under this build's `-Xfatal-warnings` settings (`WildcardImportSpec`). Note that this is weaker than the sealed-hierarchy guarantee on Scala 2.13 below: exhaustivity here depends on the compiler decomposing the opaque union, so prefer `fold` when you need a guarantee that is independent of compiler behavior. `Present(v)` matches both a raw value and a `Present(...)` wrapper, at the cost of one `Some` allocation per present match (inherent to the `Option`-returning extractor protocol).
|
|
112
|
+
|
|
113
|
+
For production code, prefer `fold`, which is exhaustive, warning-free, and zero-allocation:
|
|
114
|
+
|
|
115
|
+
```scala
|
|
116
|
+
import zio.blocks.maybe._
|
|
117
|
+
|
|
118
|
+
val maybe: Maybe[Int] = Maybe.present(42)
|
|
119
|
+
val description: String = maybe.fold("absent")(v => s"present ($v)")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Scala 2.13 parity: on Scala 2.13, absent is the non-null case object `MaybeValue.Absent`, so it **can** be matched explicitly — the sealed `MaybeValue` trait (`Present` | `Absent`) gives compiler-checked exhaustivity:
|
|
123
|
+
|
|
124
|
+
```scala
|
|
125
|
+
import zio.blocks.maybe._
|
|
126
|
+
|
|
127
|
+
val maybe: Maybe[Int] = Maybe.present(42)
|
|
128
|
+
val description: String = maybe match {
|
|
129
|
+
case MaybeValue.Present(v) => s"present ($v)"
|
|
130
|
+
case MaybeValue.Absent => "absent"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
So on Scala 2.13 the sealed `MaybeValue` form is the compiler-checked native idiom; on Scala 3 the opaque union with `Absent` object enables `case Absent` while preserving the zero-allocation encoding.
|
|
135
|
+
|
|
32
136
|
## Installation
|
|
33
137
|
|
|
34
138
|
Add the `zio-blocks-maybe` module to your build:
|
|
35
139
|
|
|
36
140
|
```scala
|
|
37
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-maybe" % "0.0.
|
|
141
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-maybe" % "0.0.55"
|
|
38
142
|
```
|
|
39
143
|
|
|
40
144
|
For Scala.js:
|
|
41
145
|
|
|
42
146
|
```scala
|
|
43
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-maybe" % "0.0.
|
|
147
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-maybe" % "0.0.55"
|
|
44
148
|
```
|
|
45
149
|
|
|
46
150
|
Supported Scala versions: 2.13.x and 3.x
|
|
@@ -84,7 +188,7 @@ Here are key patterns for working effectively with `Maybe`:
|
|
|
84
188
|
|
|
85
189
|
### Present and Absent States
|
|
86
190
|
|
|
87
|
-
Every `Maybe` is either present (holds a
|
|
191
|
+
Every `Maybe` is either present (holds a value, possibly `null`) or absent (the `Absent` singleton). Test the state with predicates:
|
|
88
192
|
|
|
89
193
|
```scala
|
|
90
194
|
import zio.blocks.maybe._
|
|
@@ -219,20 +323,28 @@ Wraps a value in `Maybe`, treating `null` as `Maybe.absent`:
|
|
|
219
323
|
```scala
|
|
220
324
|
import zio.blocks.maybe._
|
|
221
325
|
|
|
222
|
-
val present = Maybe(42)
|
|
223
|
-
val absent: Maybe[String]
|
|
326
|
+
val present: Maybe[Int] = Maybe(42) // Maybe[Int] containing 42
|
|
327
|
+
val absent: Maybe[String] = Maybe(null.asInstanceOf[String]) // Maybe.absent (the Absent object)
|
|
224
328
|
```
|
|
225
329
|
|
|
330
|
+
> **Note:** `Maybe.apply` collapses `null` to `Maybe.absent`. Use `Maybe.present` when you need to preserve present-ness even for `null` values (e.g., in nested `Maybe`s).
|
|
331
|
+
|
|
226
332
|
#### Maybe.present
|
|
227
333
|
|
|
228
|
-
Explicitly wraps a
|
|
334
|
+
Explicitly wraps a value, preserving present-ness even for `null`:
|
|
229
335
|
|
|
230
336
|
```scala
|
|
231
337
|
import zio.blocks.maybe._
|
|
232
338
|
|
|
233
339
|
val value: Maybe[Int] = Maybe.present(100)
|
|
340
|
+
|
|
341
|
+
// Nested case: present of an absent Maybe produces Present(Absent), not absence
|
|
342
|
+
val nested: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
|
|
343
|
+
// nested is Present(Absent), distinguishable from Maybe.absent
|
|
234
344
|
```
|
|
235
345
|
|
|
346
|
+
> **Note:** Unlike `Maybe.apply`, `Maybe.present` preserves present-ness even for `null`. A non-null value is returned as-is (zero allocation). A `null` value is wrapped in `Present(null)`, which is distinguishable from `Maybe.absent` (the `Absent` object). This is what makes nested `Maybe`s sound.
|
|
347
|
+
|
|
236
348
|
#### Maybe.absent
|
|
237
349
|
|
|
238
350
|
Creates an absent value for any type:
|
|
@@ -393,9 +505,14 @@ Unwraps a nested `Maybe`:
|
|
|
393
505
|
```scala
|
|
394
506
|
import zio.blocks.maybe._
|
|
395
507
|
|
|
396
|
-
val nested: Maybe[Maybe[Int]] = Maybe.
|
|
508
|
+
val nested: Maybe[Maybe[Int]] = Maybe.fromOption(Some(Maybe.fromOption(Some(42))))
|
|
397
509
|
val flat: Maybe[Int] = nested.flatten
|
|
398
510
|
println(flat.get) // 42
|
|
511
|
+
|
|
512
|
+
// Nested present-of-absent flattens to absent
|
|
513
|
+
val nestedAbsent: Maybe[Maybe[Int]] = Maybe.present(Maybe.absent[Int])
|
|
514
|
+
val flatAbsent: Maybe[Int] = nestedAbsent.flatten
|
|
515
|
+
println(flatAbsent.isAbsent) // true
|
|
399
516
|
```
|
|
400
517
|
|
|
401
518
|
### Filtering
|
package/reference/media-type.md
CHANGED
|
@@ -75,13 +75,13 @@ textAny.matches(html) // true
|
|
|
75
75
|
Add the following to your `build.sbt`:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-mediatype" % "0.0.55"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
For cross-platform projects (Scala.js):
|
|
82
82
|
|
|
83
83
|
```scala
|
|
84
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.
|
|
84
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-mediatype" % "0.0.55"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/mux.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: mux
|
|
3
|
+
title: "Mux"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Mux[Id, In, Out]` is a zero-dependency, cross-platform multiplexed stream coordinator. It manages many independent streams over one shared transport, where each stream is keyed by an `Id`.
|
|
7
|
+
|
|
8
|
+
Use it for:
|
|
9
|
+
- HTTP/2 stream multiplexing
|
|
10
|
+
- WebSocket subprotocols
|
|
11
|
+
- Any ID-keyed protocol that needs independent stream lifecycles
|
|
12
|
+
|
|
13
|
+
Key properties:
|
|
14
|
+
- Thread-safe registry operations and multi-producer stream writes
|
|
15
|
+
- Lock-free on JVM for queue operations
|
|
16
|
+
- Virtual-thread-friendly
|
|
17
|
+
- JVM uses ring buffers for stream queues
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
`Mux` owns the stream registry. Protocol code uses `offerInbound` and `takeOutbound`, while application code uses `send` and `receive`.
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
User code → send(In) → outbound queue → Protocol reads via takeOutbound()
|
|
25
|
+
Protocol → offerInbound(Out) → inbound queue → User code reads via receive()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Each stream has separate inbound and outbound queues, so traffic for one ID stays isolated from the others. Half-close maps cleanly to RFC 9113 stream states, which makes the API a good fit for HTTP/2-style protocols.
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
The `Mux` API is cross-compiled: Scala 3 uses zero-cost union return types, while Scala 2 uses `Either`.
|
|
33
|
+
|
|
34
|
+
### Scala 3
|
|
35
|
+
|
|
36
|
+
```scala
|
|
37
|
+
trait Mux[Id, In, Out] {
|
|
38
|
+
def open(id: Id): MuxStream[Id, In, Out] | MuxError
|
|
39
|
+
def get(id: Id): Option[MuxStream[Id, In, Out]]
|
|
40
|
+
def cancel(id: Id, reason: MuxError): Unit
|
|
41
|
+
def closeAll(reason: MuxError): Unit
|
|
42
|
+
def activeCount: Int
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
trait MuxStream[Id, In, Out] {
|
|
46
|
+
def id: Id
|
|
47
|
+
def send(msg: In): Unit | MuxError
|
|
48
|
+
def receive(): Option[Out] | MuxError
|
|
49
|
+
def offerInbound(msg: Out): Unit | MuxError
|
|
50
|
+
def takeOutbound(): Option[In] | MuxError
|
|
51
|
+
def halfClose(): Unit
|
|
52
|
+
def signalRemoteClose(): Unit
|
|
53
|
+
def isClosed: Boolean
|
|
54
|
+
def isHalfClosed: Boolean
|
|
55
|
+
def close(): Unit
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
sealed trait MuxError
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Scala 2
|
|
62
|
+
|
|
63
|
+
```scala
|
|
64
|
+
trait Mux[Id, In, Out] {
|
|
65
|
+
def open(id: Id): Either[MuxError, MuxStream[Id, In, Out]]
|
|
66
|
+
def get(id: Id): Option[MuxStream[Id, In, Out]]
|
|
67
|
+
def cancel(id: Id, reason: MuxError): Unit
|
|
68
|
+
def closeAll(reason: MuxError): Unit
|
|
69
|
+
def activeCount: Int
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
trait MuxStream[Id, In, Out] {
|
|
73
|
+
def id: Id
|
|
74
|
+
def send(msg: In): Either[MuxError, Unit]
|
|
75
|
+
def receive(): Either[MuxError, Option[Out]]
|
|
76
|
+
def offerInbound(msg: Out): Either[MuxError, Unit]
|
|
77
|
+
def takeOutbound(): Either[MuxError, Option[In]]
|
|
78
|
+
def halfClose(): Unit
|
|
79
|
+
def signalRemoteClose(): Unit
|
|
80
|
+
def isClosed: Boolean
|
|
81
|
+
def isHalfClosed: Boolean
|
|
82
|
+
def close(): Unit
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The semantics are the same on both versions; only the surface return types differ.
|
|
87
|
+
|
|
88
|
+
### Factory
|
|
89
|
+
|
|
90
|
+
```scala
|
|
91
|
+
object Mux {
|
|
92
|
+
def apply[Id, In, Out](capacity: Int): Mux[Id, In, Out]
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Core Operations
|
|
97
|
+
|
|
98
|
+
- `open(id)` opens a new stream
|
|
99
|
+
- `get(id)` looks up an active stream
|
|
100
|
+
- `cancel(id, reason)` closes one stream with an error
|
|
101
|
+
- `closeAll(reason)` closes every active stream
|
|
102
|
+
- `activeCount` reports how many streams are open
|
|
103
|
+
|
|
104
|
+
### Per-stream Operations
|
|
105
|
+
|
|
106
|
+
- `send(msg)` queues outbound data for the protocol layer
|
|
107
|
+
- `receive()` reads inbound data for user code
|
|
108
|
+
- `offerInbound(msg)` delivers data from the protocol layer
|
|
109
|
+
- `takeOutbound()` drains outbound data for the protocol layer
|
|
110
|
+
- `halfClose()` marks local send as finished
|
|
111
|
+
- `signalRemoteClose()` marks remote send as finished
|
|
112
|
+
- `close()` fully closes the stream immediately; buffered inbound messages can still be drained before the terminal error is observed
|
|
113
|
+
|
|
114
|
+
### `MuxError`
|
|
115
|
+
|
|
116
|
+
Error cases:
|
|
117
|
+
|
|
118
|
+
- `MuxError.StreamClosed(id)`
|
|
119
|
+
- `MuxError.CapacityExceeded(limit)` — maximum concurrent streams reached
|
|
120
|
+
- `MuxError.QueueFull(queueCapacity)` — per-stream message queue is full (backpressure)
|
|
121
|
+
- `MuxError.Cancelled(id, reason)`
|
|
122
|
+
- `MuxError.MuxClosed`
|
|
123
|
+
- `MuxError.ProtocolError(message)` — e.g., null message, invalid state transition
|
|
124
|
+
|
|
125
|
+
## Examples
|
|
126
|
+
|
|
127
|
+
### Basic Usage
|
|
128
|
+
|
|
129
|
+
Scala 3:
|
|
130
|
+
|
|
131
|
+
```scala
|
|
132
|
+
import zio.blocks.mux.*
|
|
133
|
+
|
|
134
|
+
val mux = Mux[Int, String, String](capacity = 100)
|
|
135
|
+
|
|
136
|
+
mux.open(1) match {
|
|
137
|
+
case stream: MuxStream[Int, String, String] =>
|
|
138
|
+
stream.offerInbound("hello from peer")
|
|
139
|
+
val msg = stream.receive() // Some("hello from peer")
|
|
140
|
+
stream.send("response")
|
|
141
|
+
val out = stream.takeOutbound() // Some("response")
|
|
142
|
+
case err: MuxError =>
|
|
143
|
+
println(s"open failed: $err")
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Scala 2 uses the same flow with `Either`:
|
|
148
|
+
|
|
149
|
+
```scala
|
|
150
|
+
import zio.blocks.mux._
|
|
151
|
+
|
|
152
|
+
val mux = Mux[Int, String, String](capacity = 100)
|
|
153
|
+
|
|
154
|
+
mux.open(1) match {
|
|
155
|
+
case Right(stream) =>
|
|
156
|
+
stream.offerInbound("hello from peer")
|
|
157
|
+
val msg = stream.receive() // Right(Some("hello from peer"))
|
|
158
|
+
stream.send("response")
|
|
159
|
+
val out = stream.takeOutbound() // Right(Some("response"))
|
|
160
|
+
case Left(err) =>
|
|
161
|
+
println(s"open failed: $err")
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### HTTP/2-style Multiplexing
|
|
166
|
+
|
|
167
|
+
```scala
|
|
168
|
+
import zio.blocks.mux.*
|
|
169
|
+
|
|
170
|
+
final case class Request(path: String)
|
|
171
|
+
final case class Response(status: Int)
|
|
172
|
+
|
|
173
|
+
val mux = Mux[Int, Request, Response](capacity = 1000)
|
|
174
|
+
|
|
175
|
+
// Demuxer receives frames, routes by stream ID
|
|
176
|
+
def onFrame(streamId: Int, data: Response): Unit = {
|
|
177
|
+
mux.get(streamId).foreach(_.offerInbound(data))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Application opens streams for requests
|
|
181
|
+
mux.open(7) match {
|
|
182
|
+
case stream: MuxStream[Int, Request, Response] =>
|
|
183
|
+
stream.send(Request("/docs"))
|
|
184
|
+
|
|
185
|
+
// ... later
|
|
186
|
+
val response = stream.receive()
|
|
187
|
+
stream.halfClose()
|
|
188
|
+
case err: MuxError =>
|
|
189
|
+
println(s"open failed: $err")
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
In Scala 2, pattern match on `Right(stream)` / `Left(err)` instead.
|
|
194
|
+
|
|
195
|
+
### Half-close Lifecycle
|
|
196
|
+
|
|
197
|
+
```scala
|
|
198
|
+
import zio.blocks.mux.*
|
|
199
|
+
|
|
200
|
+
val mux = Mux[Int, String, String](capacity = 10)
|
|
201
|
+
|
|
202
|
+
mux.open(1) match {
|
|
203
|
+
case stream: MuxStream[Int, String, String] =>
|
|
204
|
+
stream.send("last message")
|
|
205
|
+
stream.halfClose()
|
|
206
|
+
|
|
207
|
+
// Can still receive after half-close
|
|
208
|
+
val msg = stream.receive()
|
|
209
|
+
|
|
210
|
+
// send() after halfClose returns an error
|
|
211
|
+
stream.send("nope") // MuxError.StreamClosed(1)
|
|
212
|
+
case err: MuxError =>
|
|
213
|
+
println(s"open failed: $err")
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Scala 2 returns `Left(MuxError.StreamClosed(1))` for the final `send`.
|
|
218
|
+
|
|
219
|
+
### Graceful Shutdown
|
|
220
|
+
|
|
221
|
+
```scala
|
|
222
|
+
import zio.blocks.mux.*
|
|
223
|
+
|
|
224
|
+
val mux = Mux[Int, String, String](capacity = 10)
|
|
225
|
+
|
|
226
|
+
// Cancel a single stream
|
|
227
|
+
mux.cancel(42, MuxError.Cancelled(42, "timeout"))
|
|
228
|
+
|
|
229
|
+
// Shut down everything
|
|
230
|
+
mux.closeAll(MuxError.MuxClosed)
|
|
231
|
+
assert(mux.activeCount == 0)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Architecture
|
|
235
|
+
|
|
236
|
+
`Mux` keeps a registry of active streams and gives each stream its own inbound and outbound queues. The protocol layer never talks to user code directly, it only moves messages through `offerInbound` and `takeOutbound`.
|
|
237
|
+
|
|
238
|
+
Half-close models the usual protocol lifecycle:
|
|
239
|
+
|
|
240
|
+
- local side done sending
|
|
241
|
+
- remote side done sending
|
|
242
|
+
- both sides done, stream fully closed
|
|
243
|
+
|
|
244
|
+
## Performance
|
|
245
|
+
|
|
246
|
+
- JVM: `MpscRingBuffer` for inbound and outbound, lock-free and zero-alloc for multi-producer writes
|
|
247
|
+
- JVM: `VarHandle` CAS for stream state transitions
|
|
248
|
+
- JS: `ArrayDeque` fallback
|
|
249
|
+
- No `synchronized`, so it stays friendly to virtual threads
|
|
250
|
+
|
|
251
|
+
## See Also
|
|
252
|
+
|
|
253
|
+
- [Streams](streams/index.md) -- the pull-based `Stream`, `Reader`, `Sink`, and `Writer` module. `Mux` is not built on it, but the two are the same concurrency-infrastructure family, and a stream per multiplexed channel is the natural pairing when you are coordinating many keyed streams over one transport.
|
|
254
|
+
- [Async Execution](streams/execution-and-compatibility/async-execution.md) -- how a stream runs without blocking, which is what you want on each side of a `Mux` channel.
|
package/reference/mux.mdx
CHANGED
|
@@ -47,13 +47,13 @@ Without multiplexing, protocols must open a new connection per concurrent operat
|
|
|
47
47
|
Add the dependency to your build:
|
|
48
48
|
|
|
49
49
|
```scala
|
|
50
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-mux" % "
|
|
50
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-mux" % "0.0.55"
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
For Scala.js:
|
|
54
54
|
|
|
55
55
|
```scala
|
|
56
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-mux" % "
|
|
56
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-mux" % "0.0.55"
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Supported Scala versions: 2.13.x and 3.x
|
|
@@ -821,3 +821,8 @@ val inMux = mux.get(1)
|
|
|
821
821
|
- JVM: `VarHandle` CAS for stream state transitions
|
|
822
822
|
- JS: `ArrayDeque` fallback
|
|
823
823
|
- No `synchronized`, so it stays friendly to virtual threads
|
|
824
|
+
|
|
825
|
+
## See Also
|
|
826
|
+
|
|
827
|
+
- [Streams](streams/index.md) -- the pull-based `Stream`, `Reader`, `Sink`, and `Writer` module. `Mux` is not built on it, but the two are the same concurrency-infrastructure family, and a stream per multiplexed channel is the natural pairing when you are coordinating many keyed streams over one transport.
|
|
828
|
+
- [Async Execution](streams/execution-and-compatibility/async-execution.md) -- how a stream runs without blocking, which is what you want on each side of a `Mux` channel.
|
package/reference/openapi.md
CHANGED
|
@@ -27,16 +27,16 @@ The OpenAPI module bridges the gap by letting you author API specs as Scala code
|
|
|
27
27
|
## Installation
|
|
28
28
|
|
|
29
29
|
```scala
|
|
30
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-openapi" % "0.0.
|
|
30
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-openapi" % "0.0.55"
|
|
31
31
|
|
|
32
32
|
// You'll also need the schema module for Schema[A] integration:
|
|
33
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
33
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.55"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
For Scala.js:
|
|
37
37
|
|
|
38
38
|
```scala
|
|
39
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-openapi" % "0.0.
|
|
39
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-openapi" % "0.0.55"
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
Supported Scala versions: 2.13.x and 3.x.
|