@zio.dev/zio-blocks 0.0.51 → 0.0.56
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 -559
- 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/endpoint.md +1 -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.mdx +7 -2
- package/reference/openapi.md +3 -3
- package/reference/projection.md +654 -0
- package/reference/resource-management/index.md +1 -1
- 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/json/json.md +1 -0
- 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 +2 -2
- 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 +365 -185
- 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
|
@@ -38,10 +38,12 @@ This separation keeps your domain logic portable and testable while maintaining
|
|
|
38
38
|
|
|
39
39
|
## Getting Started
|
|
40
40
|
|
|
41
|
-
Start with the [HTTP Model](./model.md) to understand the core data types and how they compose. Then explore [Schema-Based Typed Access](./schema.md) to learn how to safely extract and validate HTTP parameters and headers.
|
|
41
|
+
Start with the [HTTP Model](./model.md) to understand the core data types and how they compose. [Header](./headers.md) covers the typed header model — the 75 built-in headers, the codec type class behind them, and the parse cache — and [ServerSentEvent](./server-sent-event.md) covers the SSE envelope and its wire format. Then explore [Schema-Based Typed Access](./schema.md) to learn how to safely extract and validate HTTP parameters and headers.
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
45
45
|
**Modules:**
|
|
46
46
|
- [`zio-http-model`](./model.md) — Core immutable data types for HTTP
|
|
47
|
+
- [`zio-http-model`](./headers.md) — Typed header model and the `Header.Codec` type class
|
|
48
|
+
- [`zio-http-model`](./server-sent-event.md) — Server-Sent Event envelope and payload encoders
|
|
47
49
|
- [`zio-http-model-schema`](./schema.md) — Schema-based typed extraction for query parameters and headers
|
|
@@ -3,7 +3,7 @@ id: model
|
|
|
3
3
|
title: "HTTP Model"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`zio-http-model` is a **
|
|
6
|
+
`zio-http-model` is a **runtime-independent HTTP data model** for building HTTP clients and servers. It provides immutable types representing all HTTP concepts: requests, responses, headers, URLs, paths, query parameters, methods, status codes, versions, cookies, and forms.
|
|
7
7
|
|
|
8
8
|
Core types: `Request`, `Response`, `URL`, `Headers`, `Body`, `Method`, `Status`, `Version`, `Scheme`, `Path`, `QueryParams`, `ContentType`, `RequestCookie`, `ResponseCookie`, `Form`.
|
|
9
9
|
|
|
@@ -42,19 +42,19 @@ Imagine building a distributed system where you need an HTTP client to call exte
|
|
|
42
42
|
This creates a coupling problem:
|
|
43
43
|
|
|
44
44
|
**Scenario 1: Sharing Types Across Layers**
|
|
45
|
-
You want your client request logic (building a request to send) to use the same types as your server request handling (receiving and parsing a request). But your HTTP library makes this difficult — the `Request` type is tied to
|
|
45
|
+
You want your client request logic (building a request to send) to use the same types as your server request handling (receiving and parsing a request). But your HTTP library makes this difficult — the `Request` type is tied to a particular runtime, file-stream implementation, or async framework. Sharing becomes messy.
|
|
46
46
|
|
|
47
|
-
**Scenario 2: Testing Without
|
|
48
|
-
You're writing unit tests for your request-building logic. You want to serialize a request to JSON for snapshots, or cache requests for debugging. But your `Request` type requires pulling in
|
|
47
|
+
**Scenario 2: Testing Without an HTTP Runtime**
|
|
48
|
+
You're writing unit tests for your request-building logic. You want to serialize a request to JSON for snapshots, or cache requests for debugging. But your `Request` type requires pulling in an HTTP runtime or other baggage you don't need in tests. A simple unit test becomes a production-grade setup.
|
|
49
49
|
|
|
50
50
|
**Scenario 3: Lock-In**
|
|
51
51
|
You've built your entire API client around ZIO's HTTP library, but your team decides to use Akka for one microservice. Now your request/response types aren't portable — they're coupled to ZIO. Refactoring is painful.
|
|
52
52
|
|
|
53
|
-
### The Solution:
|
|
53
|
+
### The Solution: Runtime-Independent HTTP Data
|
|
54
54
|
|
|
55
55
|
`zio-http-model` separates **protocol concerns** (representing HTTP messages) from **effect concerns** (actually sending/receiving them). It provides:
|
|
56
56
|
|
|
57
|
-
- **
|
|
57
|
+
- **Immutable message values** — `Request`, `Response`, `URL`, and `Headers` are immutable data. `Body` wraps a pull-based byte stream; materializing the complete body is represented by the lightweight `Async` abstraction.
|
|
58
58
|
|
|
59
59
|
- **No dependency on an HTTP runtime** — Not coupled to ZIO HTTP, Akka, or a server/client implementation. The module depends on ZIO Blocks primitives such as Chunk, MediaType, and Stream.
|
|
60
60
|
|
|
@@ -70,13 +70,13 @@ This separation is powerful: you can build, manipulate, serialize, and test HTTP
|
|
|
70
70
|
Add the following to your `build.sbt`:
|
|
71
71
|
|
|
72
72
|
```scala
|
|
73
|
-
libraryDependencies += "dev.zio" %% "zio-http-model" % "0.0.
|
|
73
|
+
libraryDependencies += "dev.zio" %% "zio-http-model" % "0.0.56"
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
For cross-platform projects (Scala.js):
|
|
77
77
|
|
|
78
78
|
```scala
|
|
79
|
-
libraryDependencies += "dev.zio" %%% "zio-http-model" % "0.0.
|
|
79
|
+
libraryDependencies += "dev.zio" %%% "zio-http-model" % "0.0.56"
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
Supported Scala versions: Scala 2.13 and Scala 3.x. The artifacts are cross-platform for JVM and Scala.js.
|
|
@@ -635,37 +635,79 @@ val allIds = params.get("id") // Some(Chunk("1", "2", "3"))
|
|
|
635
635
|
|
|
636
636
|
## Headers
|
|
637
637
|
|
|
638
|
-
`Headers` is an immutable, case-insensitive collection of HTTP
|
|
638
|
+
`Headers` is an immutable, case-insensitive collection of HTTP header fields. It stores names pre-lowercased and raw values as strings, allows several entries with the same name, and parses a value into a typed `Header` only when a typed read asks for it:
|
|
639
639
|
|
|
640
640
|
```scala
|
|
641
|
-
final class Headers private[http] (...)
|
|
641
|
+
final class Headers private[http] (...) {
|
|
642
|
+
def size: Int
|
|
643
|
+
def get[A](headerCodec: Header.Codec[A]): Option[A]
|
|
644
|
+
def rawGet(name: String): Option[String]
|
|
645
|
+
def add(header: Header): Headers
|
|
646
|
+
def set(header: Header): Headers
|
|
647
|
+
def remove(name: String): Headers
|
|
648
|
+
}
|
|
642
649
|
```
|
|
643
650
|
|
|
651
|
+
This section covers the collection itself. The 75 built-in typed headers, the codec type class behind them, the parse cache, and the validation rules all live on the [Header](./headers.md) page.
|
|
652
|
+
|
|
644
653
|
### Creating Headers
|
|
645
654
|
|
|
646
|
-
|
|
655
|
+
`Headers.apply` takes name-value pairs, and `Headers.empty` is the starting point for a builder chain:
|
|
647
656
|
|
|
648
657
|
```scala
|
|
649
658
|
import zio.http.Headers
|
|
650
659
|
|
|
651
|
-
val
|
|
652
|
-
val headers2 = Headers.empty
|
|
660
|
+
val headers = Headers("content-type" -> "application/json", "cache-control" -> "no-cache")
|
|
653
661
|
```
|
|
654
662
|
|
|
655
|
-
|
|
663
|
+
Names are lowercased as they are stored, so the collection reports them in canonical form regardless of how they were written:
|
|
664
|
+
|
|
665
|
+
```scala
|
|
666
|
+
Headers("Content-Type" -> "application/json").toList
|
|
667
|
+
// res27: List[Tuple2[String, String]] = List(
|
|
668
|
+
// ("content-type", "application/json")
|
|
669
|
+
// )
|
|
670
|
+
```
|
|
656
671
|
|
|
657
|
-
|
|
672
|
+
### Reading Raw Values
|
|
673
|
+
|
|
674
|
+
Three methods read the stored string without parsing, differing in which entry they pick when a name repeats:
|
|
658
675
|
|
|
659
676
|
```scala
|
|
660
|
-
|
|
677
|
+
headers.rawGet("Content-Type")
|
|
678
|
+
// res28: Option[String] = Some("application/json")
|
|
679
|
+
headers.rawGetAll("cache-control")
|
|
680
|
+
// res29: Chunk[String] = IndexedSeq("no-cache")
|
|
681
|
+
headers.has("content-type")
|
|
682
|
+
// res30: Boolean = true
|
|
683
|
+
```
|
|
661
684
|
|
|
662
|
-
|
|
685
|
+
Lookups are case-insensitive, and `Headers#contains` is an alias for `Headers#has`. For typed reads — `Headers#get`, `Headers#getAll`, and `Headers#getLast`, which decode into a `Header` — see [Header](./headers.md).
|
|
686
|
+
|
|
687
|
+
### Modifying Headers
|
|
663
688
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
689
|
+
`Headers#add` appends and `Headers#set` replaces every entry with the same name, which is the distinction that matters for multi-value headers:
|
|
690
|
+
|
|
691
|
+
```scala
|
|
692
|
+
val twice = headers.add("set-cookie", "a=1").add("set-cookie", "b=2")
|
|
693
|
+
val replaced = twice.set("set-cookie", "c=3")
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
Appending keeps both cookies while setting collapses them to one:
|
|
697
|
+
|
|
698
|
+
```scala
|
|
699
|
+
twice.rawGetAll("set-cookie")
|
|
700
|
+
// res31: Chunk[String] = IndexedSeq("a=1", "b=2")
|
|
701
|
+
replaced.rawGetAll("set-cookie")
|
|
702
|
+
// res32: Chunk[String] = IndexedSeq("c=3")
|
|
667
703
|
```
|
|
668
704
|
|
|
705
|
+
`Headers#remove` drops every entry with a name, `Headers#++` concatenates two collections without deduplicating, and `Headers#toList` and `Headers#toChunk` expose the pairs for iteration. Every operation returns a new `Headers`.
|
|
706
|
+
|
|
707
|
+
:::warning[Names and values are validated by throwing]
|
|
708
|
+
A header name must be an HTTP token and a value must not contain CR or LF — the latter is what prevents response splitting. The mutating methods enforce both with `IllegalArgumentException`, so check untrusted input with `Headers.validateName` or `Headers.validateValue` first. See [Header](./headers.md) for the rules and the safe pre-checks.
|
|
709
|
+
:::
|
|
710
|
+
|
|
669
711
|
---
|
|
670
712
|
|
|
671
713
|
## Body
|
|
@@ -748,14 +790,37 @@ val body = Body.fromString("Hello!", Charset.UTF8)
|
|
|
748
790
|
body.length // Some(6)
|
|
749
791
|
body.isEmpty // false
|
|
750
792
|
body.nonEmpty // true
|
|
751
|
-
body.asString() // "Hello!" (UTF-8 default)
|
|
752
|
-
body.asString(Charset.ASCII) // "Hello!" (explicit charset)
|
|
753
|
-
body.toChunk // Chunk[Byte](72, 101, 108, 108, 111, 33)
|
|
754
793
|
body.toStream // Stream[Nothing, Byte]
|
|
755
|
-
body.toArray // Array[Byte](72, 101, 108, 108, 111, 33)
|
|
756
794
|
body.contentType // ContentType(text/plain; charset=UTF-8)
|
|
795
|
+
|
|
796
|
+
val utf8: zio.blocks.async.Async[String] = body.asStringAsync()
|
|
797
|
+
val ascii: zio.blocks.async.Async[String] = body.asStringAsync(Charset.ASCII)
|
|
798
|
+
val bytes: zio.blocks.async.Async[Array[Byte]] = body.toArrayAsync
|
|
799
|
+
val chunk: zio.blocks.async.Async[zio.blocks.chunk.Chunk[Byte]] = body.toChunkAsync
|
|
800
|
+
val fromContentType: zio.blocks.async.Async[String] = body.asStringFromContentTypeAsync
|
|
801
|
+
val text: zio.blocks.async.Async[String] = body.textAsync
|
|
802
|
+
```
|
|
803
|
+
|
|
804
|
+
The five asynchronous materializers—`toChunkAsync`, `toArrayAsync`, `asStringAsync`, `asStringFromContentTypeAsync`, and the `textAsync` alias—are defined in shared sources and are available on both JVM and Scala.js. `asStringFromContentTypeAsync` and `textAsync` use the charset declared by the content type, falling back to UTF-8.
|
|
805
|
+
|
|
806
|
+
`Body` retains synchronous twins with the corresponding result types:
|
|
807
|
+
|
|
808
|
+
```scala
|
|
809
|
+
import zio.http.{Body, Charset}
|
|
810
|
+
import zio.blocks.chunk.Chunk
|
|
811
|
+
|
|
812
|
+
val body = Body.fromString("Hello!", Charset.UTF8)
|
|
813
|
+
|
|
814
|
+
val chunk: Chunk[Byte] = body.toChunk
|
|
815
|
+
val bytes: Array[Byte] = body.toArray
|
|
816
|
+
val utf8: String = body.asString()
|
|
817
|
+
val ascii: String = body.asString(Charset.ASCII)
|
|
818
|
+
val fromContentType: String = body.asStringFromContentType
|
|
819
|
+
val text: String = body.text
|
|
757
820
|
```
|
|
758
821
|
|
|
822
|
+
The synchronous methods remain available on both platforms for compatibility and for streams that complete synchronously. Prefer the asynchronous methods whenever a body can suspend; Scala.js cannot block while waiting for pending asynchronous work.
|
|
823
|
+
|
|
759
824
|
---
|
|
760
825
|
|
|
761
826
|
## ContentType
|
|
@@ -1328,69 +1393,36 @@ This design shines in three ways:
|
|
|
1328
1393
|
|
|
1329
1394
|
**Simplicity**: Your code is clean — you just ask for headers by name, and http-model handles parsing transparently. No manual string manipulation or error handling on your end.
|
|
1330
1395
|
|
|
1331
|
-
###
|
|
1396
|
+
### Stream-Backed Bodies
|
|
1332
1397
|
|
|
1333
1398
|
Let's say you're downloading a 500MB video file over HTTP. Should your `Body` object represent that as:
|
|
1334
1399
|
|
|
1335
1400
|
**Option A: A single `Chunk[Byte]` with all 500MB in memory?**
|
|
1336
1401
|
|
|
1337
1402
|
```scala
|
|
1338
|
-
val body = Body(
|
|
1403
|
+
val body = Body.fromChunk(Chunk[Byte](/* 500MB of bytes */))
|
|
1339
1404
|
// Everything loaded into RAM at once
|
|
1340
1405
|
```
|
|
1341
1406
|
|
|
1342
1407
|
**Option B: A Stream that yields bytes incrementally as they arrive?**
|
|
1343
1408
|
|
|
1344
1409
|
```scala
|
|
1345
|
-
val
|
|
1410
|
+
val byteStream: Stream[Nothing, Byte] = /* yields bytes as they download */
|
|
1411
|
+
val body = Body.fromStream(byteStream)
|
|
1346
1412
|
// Only a small buffer in RAM; the rest comes from the network
|
|
1347
1413
|
```
|
|
1348
1414
|
|
|
1349
1415
|
Most HTTP libraries choose Option B for large files — streaming makes sense when you want to process data *as it arrives* without loading everything into memory first.
|
|
1350
1416
|
|
|
1351
|
-
**http-model chooses
|
|
1417
|
+
**http-model chooses a stream-backed body with runtime-independent asynchronous materialization.**
|
|
1352
1418
|
|
|
1353
1419
|
#### The Streaming Trade-off
|
|
1354
1420
|
|
|
1355
|
-
Streaming
|
|
1356
|
-
|
|
1357
|
-
**Streaming requires effects:**
|
|
1358
|
-
|
|
1359
|
-
```scala
|
|
1360
|
-
// With streaming, reading a body becomes an effect:
|
|
1361
|
-
val body: Body = request.body
|
|
1362
|
-
val bytes: IO[Chunk[Byte]] = body.stream.runCollect()
|
|
1363
|
-
// Reading the body is now an IO operation, not a pure value!
|
|
1364
|
-
```
|
|
1365
|
-
|
|
1366
|
-
This couples `Body` to a specific effect system (ZIO, Cats Effect, Scala Futures, etc.). Different effect systems have different streaming abstractions, and your `Body` type would need to know about all of them — or you'd lock users into one.
|
|
1367
|
-
|
|
1368
|
-
**Streaming requires error handling:**
|
|
1369
|
-
```scala
|
|
1370
|
-
// With streaming, errors can happen mid-stream:
|
|
1371
|
-
body.stream.fold(
|
|
1372
|
-
error => handleNetworkFailure(error), // Network cut out!
|
|
1373
|
-
chunk => processChunk(chunk),
|
|
1374
|
-
() => done()
|
|
1375
|
-
)
|
|
1376
|
-
// You must handle errors at every chunk boundary
|
|
1377
|
-
```
|
|
1378
|
-
|
|
1379
|
-
**Streaming complicates testing:**
|
|
1380
|
-
|
|
1381
|
-
```scala
|
|
1382
|
-
// Testing code that consumes streams is verbose:
|
|
1383
|
-
val testStream = Stream(
|
|
1384
|
-
Chunk(1, 2, 3),
|
|
1385
|
-
Chunk(4, 5, 6),
|
|
1386
|
-
Chunk(7, 8, 9)
|
|
1387
|
-
).flatMap(_.stream)
|
|
1388
|
-
// vs. just: Chunk(1, 2, 3, 4, 5, 6, 7, 8, 9)
|
|
1389
|
-
```
|
|
1421
|
+
Streaming saves memory and allows processing to begin before the complete payload arrives, but collecting the complete body may need to wait for asynchronous input. `Body` exposes that operation as `Async`, rather than coupling the HTTP model to ZIO, Cats Effect, Scala Futures, or a particular HTTP runtime.
|
|
1390
1422
|
|
|
1391
1423
|
#### http-model's Choice: Stream-Backed Bodies
|
|
1392
1424
|
|
|
1393
|
-
http-model wraps a `Stream[Nothing, Byte]` — a
|
|
1425
|
+
http-model wraps a `Stream[Nothing, Byte]` — a pull-based stream with cross-platform asynchronous materialization and no required effect runtime:
|
|
1394
1426
|
|
|
1395
1427
|
```scala
|
|
1396
1428
|
final class Body private (
|
|
@@ -1407,13 +1439,17 @@ val body = Body.fromChunk(
|
|
|
1407
1439
|
ContentType.`application/json`
|
|
1408
1440
|
)
|
|
1409
1441
|
|
|
1410
|
-
// Accessing the data:
|
|
1411
|
-
val
|
|
1412
|
-
val
|
|
1413
|
-
val
|
|
1442
|
+
// Accessing the data on JVM and Scala.js:
|
|
1443
|
+
val chunk: Async[Chunk[Byte]] = body.toChunkAsync
|
|
1444
|
+
val bytes: Async[Array[Byte]] = body.toArrayAsync
|
|
1445
|
+
val decoded: Async[String] = body.asStringAsync(Charset.UTF8)
|
|
1446
|
+
val decodedFromContentType: Async[String] = body.asStringFromContentTypeAsync
|
|
1447
|
+
val text: Async[String] = body.textAsync
|
|
1448
|
+
val len: Option[Long] = body.length
|
|
1449
|
+
val raw: Stream[Nothing, Byte] = body.toStream
|
|
1414
1450
|
```
|
|
1415
1451
|
|
|
1416
|
-
For chunk-backed bodies,
|
|
1452
|
+
For chunk-backed bodies, materialization reuses the known chunk and `length` returns `Some(n)`. For a stream without known metadata, materialization consumes the stream and `length` returns `None`. The shared `Body.scala` defines both the five `Async` methods and their synchronous compatibility twins.
|
|
1417
1453
|
|
|
1418
1454
|
```
|
|
1419
1455
|
Your Application Code
|
|
@@ -1428,12 +1464,12 @@ Your Application Code
|
|
|
1428
1464
|
(wraps/unwraps)
|
|
1429
1465
|
↓
|
|
1430
1466
|
┌─────────────────────┐
|
|
1431
|
-
│ http-model Body │ (stream-backed,
|
|
1467
|
+
│ http-model Body │ (stream-backed, async materialization)
|
|
1432
1468
|
│ (pull-based I/O) │
|
|
1433
1469
|
└─────────────────────┘
|
|
1434
1470
|
```
|
|
1435
1471
|
|
|
1436
|
-
Body
|
|
1472
|
+
Body remains independent of any HTTP or effect runtime, but materializing an opaque stream can be asynchronous. Prefer `toChunkAsync`, `toArrayAsync`, `asStringAsync`, `asStringFromContentTypeAsync`, or `textAsync` for streams that can suspend. The synchronous twins remain available on both platforms for compatibility and synchronously completing streams.
|
|
1437
1473
|
|
|
1438
1474
|
## Running the Examples
|
|
1439
1475
|
|