@zio.dev/zio-blocks 0.0.29 → 0.0.31
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/guides/compile-time-resource-safety-with-scope.md +997 -0
- package/guides/query-dsl-extending.md +1 -1
- package/guides/query-dsl-fluent-builder.md +203 -203
- package/guides/query-dsl-reified-optics.md +1 -1
- package/guides/query-dsl-sql.md +1 -1
- package/guides/zio-schema-migration.md +8 -8
- package/index.md +20 -14
- package/package.json +1 -1
- package/reference/codec.md +17 -17
- package/reference/context.md +49 -1
- package/reference/docs.md +1 -1
- package/reference/dynamic-schema.md +39 -42
- package/reference/formats.md +5 -11
- package/reference/json-schema.md +2 -2
- package/reference/json.md +34 -43
- package/reference/media-type.md +2 -2
- package/reference/modifier.md +4 -4
- package/reference/resource-management/defer-handle.md +246 -0
- package/reference/resource-management/finalization.md +286 -0
- package/reference/resource-management/finalizer.md +167 -0
- package/reference/{resource-management-di → resource-management}/index.md +3 -8
- package/reference/{resource-management-di → resource-management}/resource.md +532 -50
- package/reference/resource-management/scope.md +3026 -0
- package/reference/resource-management/unscoped.md +125 -0
- package/reference/{resource-management-di → resource-management}/wire.md +122 -28
- package/reference/schema-error.md +0 -1
- package/reference/schema-evolution/as.md +4 -4
- package/reference/schema-evolution/into.md +2 -2
- package/reference/schema-expr.md +2 -2
- package/reference/schema.md +9 -4
- package/reference/streams.md +989 -0
- package/reference/type-class-derivation.md +14 -15
- package/ringbuffer.md +1 -1
- package/sidebars.js +9 -4
- package/undocumented-report.md +1 -1
- package/reference/resource-management-di/scope.md +0 -1423
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: unscoped
|
|
3
|
+
title: "Unscoped"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`Unscoped[A]` is a marker typeclass for types that can safely escape a scope without tracking. Types with an `Unscoped` instance are considered "safe data"—they don't hold resources and can be freely extracted from a scope. Here's the definition:
|
|
7
|
+
|
|
8
|
+
```scala
|
|
9
|
+
trait Unscoped[A]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The `Unscoped` typeclass distinguishes between two categories of types:
|
|
13
|
+
|
|
14
|
+
1. **Unscoped types** (have an instance): Primitives, strings, collections, value types, and pure data. These can leave a scope without risk.
|
|
15
|
+
2. **Scoped types** (no instance): Resources like streams, connections, handles. These must remain tracked within a scope.
|
|
16
|
+
|
|
17
|
+
When the `$` operator is used to access a scoped value, if the result type has an `Unscoped` instance, it returns the value directly (unwrapped). Otherwise, it returns the value still wrapped in `$`.
|
|
18
|
+
|
|
19
|
+
## Motivation / Use Case
|
|
20
|
+
|
|
21
|
+
The exact problem: A `scoped` block automatically closes all resources when it exits. If you accidentally returned a resource (like a database connection or file handle) from the block, it would be closed—but you might try to use it later, causing a **use-after-close** crash. Here's an example (this would fail without `Unscoped`):
|
|
22
|
+
|
|
23
|
+
```scala
|
|
24
|
+
import zio.blocks.scope.{Scope, Resource}
|
|
25
|
+
|
|
26
|
+
final class Database {
|
|
27
|
+
def query(sql: String) = s"result: $sql"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Without Unscoped constraint, this compiles (BAD):
|
|
31
|
+
// val db: Database = Scope.global.scoped { scope =>
|
|
32
|
+
// val db = allocate(Resource(new Database()))
|
|
33
|
+
// db // BUG: returns the resource itself, not data extracted from it
|
|
34
|
+
// }
|
|
35
|
+
// db.query("SELECT 1") // CRASH: use-after-close (scope already closed it)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The solution: `Unscoped` makes this a **compile error** instead of a runtime bug. When a `scoped` block returns a value, that value's type must have an `Unscoped` instance—meaning the type checker verifies you're only extracting *computed results* (like `Int`, `String`, or aggregate data), not resources themselves.
|
|
39
|
+
|
|
40
|
+
You can still extract computed results by using the `$` operator to unwrap scoped values *within* the scope:
|
|
41
|
+
|
|
42
|
+
```scala
|
|
43
|
+
import zio.blocks.scope.{Scope, Resource}
|
|
44
|
+
|
|
45
|
+
Scope.global.scoped { scope =>
|
|
46
|
+
import scope._
|
|
47
|
+
|
|
48
|
+
val intValue = allocate(Resource(42))
|
|
49
|
+
// Extract the Int value (not the Resource), computed inside the scope
|
|
50
|
+
val n: Int = $(intValue)(x => x + 1)
|
|
51
|
+
|
|
52
|
+
val text = allocate(Resource("hello"))
|
|
53
|
+
// Extract the String value (not the Resource), computed inside the scope
|
|
54
|
+
val s: String = $(text)(x => x.toUpperCase)
|
|
55
|
+
|
|
56
|
+
(n, s) // Tuple of pure data: safe to return
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Returning Unscoped Data from Scopes
|
|
61
|
+
|
|
62
|
+
Extract computed results that don't hold resources:
|
|
63
|
+
|
|
64
|
+
```scala
|
|
65
|
+
import zio.blocks.scope.{Scope, Resource, Unscoped}
|
|
66
|
+
import scala.concurrent.duration.{Duration, FiniteDuration}
|
|
67
|
+
|
|
68
|
+
case class ProcessingResult(count: Int, elapsed: FiniteDuration)
|
|
69
|
+
|
|
70
|
+
object ProcessingResult {
|
|
71
|
+
implicit val unscoped: Unscoped[ProcessingResult] = new Unscoped[ProcessingResult] {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def processData(): ProcessingResult = Scope.global.scoped { scope =>
|
|
75
|
+
import scope._
|
|
76
|
+
|
|
77
|
+
val startTime = java.time.Instant.now()
|
|
78
|
+
val input = allocate(Resource(Seq(1, 2, 3, 4, 5)))
|
|
79
|
+
val count = $(input)(_.length)
|
|
80
|
+
|
|
81
|
+
val endTime = java.time.Instant.now()
|
|
82
|
+
val elapsed = java.time.Duration.between(startTime, endTime).toNanos
|
|
83
|
+
|
|
84
|
+
ProcessingResult(count, FiniteDuration(elapsed, java.util.concurrent.TimeUnit.NANOSECONDS))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val result = processData()
|
|
88
|
+
println(result)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Only create instances for **pure data types** that don't hold resources. Never create instances for types that contain connections, streams, handles, or any resource-like fields.
|
|
92
|
+
|
|
93
|
+
## Predefined Instances
|
|
94
|
+
|
|
95
|
+
All built-in instances follow a simple principle: **if a type cannot hold resources, it gets an `Unscoped` instance**. Collections inherit this property from their elements — `List[Int]` is unscoped because `Int` is unscoped.
|
|
96
|
+
|
|
97
|
+
**Primitive and atomic values** (cannot hold resources by nature):
|
|
98
|
+
- `Int`, `Long`, `Short`, `Byte`, `Char`, `Boolean`, `Float`, `Double`, `Unit`
|
|
99
|
+
- `String`, `BigInt`, `BigDecimal`
|
|
100
|
+
- `java.util.UUID`
|
|
101
|
+
|
|
102
|
+
**Collections with conditional instances** (safe when elements/entries are unscoped):
|
|
103
|
+
- Sequences: `Array[A]`, `List[A]`, `Vector[A]`, `Seq[A]`, `IndexedSeq[A]`, `Iterable[A]`
|
|
104
|
+
- Sets: `Set[A]`
|
|
105
|
+
- Maps: `Map[K, V]` (when both `K` and `V` are unscoped)
|
|
106
|
+
- Wrappers: `Option[A]`, `Either[A, B]`, `Tuple2[A, B]` through `Tuple4[A, B, C, D]`
|
|
107
|
+
- ZIO types: `zio.blocks.chunk.Chunk[A]`
|
|
108
|
+
|
|
109
|
+
**Standard library time types** (immutable, cannot hold resources):
|
|
110
|
+
- `java.time.Instant`, `LocalDate`, `LocalTime`, `LocalDateTime`, `ZonedDateTime`, `OffsetDateTime`
|
|
111
|
+
- `java.time.Duration`, `Period`, `ZoneId`, `ZoneOffset`
|
|
112
|
+
- `scala.concurrent.duration.Duration`, `FiniteDuration`
|
|
113
|
+
|
|
114
|
+
All other types (resources, handles, connections) must be manually defined if needed.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Thread Safety
|
|
118
|
+
|
|
119
|
+
`Unscoped` instances themselves are immutable and thread-safe. However, the types they mark must be truly immutable for safe concurrent use. For example, `Array[Int]` is mutable—if shared across threads without synchronization, it could cause data races.
|
|
120
|
+
|
|
121
|
+
## Integration
|
|
122
|
+
|
|
123
|
+
- [`Scope.$`](./scope.md) — the operator that uses `Unscoped`
|
|
124
|
+
- [`Resource`](./resource.md) — types that provide `Unscoped` may be wrapped in resources
|
|
125
|
+
- [`Scope`](./scope.md) — manages the lifecycle of resources
|
|
@@ -3,7 +3,7 @@ id: wire
|
|
|
3
3
|
title: "Wire"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`Wire[-In, +Out]` is a **compile-time safe recipe for constructing a service and its dependencies**. Wires describe how to construct an `Out` value given access to its dependencies via a `Context[In]` and a `Scope` for finalization
|
|
6
|
+
`Wire[-In, +Out]` is a **compile-time safe recipe for constructing a service and its dependencies**. Wires describe how to construct an `Out` value given access to its dependencies via a `Context[In]` and a `Scope` for finalization:
|
|
7
7
|
|
|
8
8
|
```scala
|
|
9
9
|
sealed trait Wire[-In, +Out] {
|
|
@@ -60,7 +60,7 @@ final class App(service: UserService) {
|
|
|
60
60
|
|
|
61
61
|
// Manual wiring:
|
|
62
62
|
Scope.global.scoped { scope =>
|
|
63
|
-
import scope
|
|
63
|
+
import scope._
|
|
64
64
|
val config = Config("jdbc:postgres://localhost/db")
|
|
65
65
|
val db = Resource.fromAutoCloseable(new Database(config)).allocate
|
|
66
66
|
val service = new UserService($(db)(identity))
|
|
@@ -73,7 +73,7 @@ With `Wire` + `Resource.from`, the macro handles the dependency graph:
|
|
|
73
73
|
|
|
74
74
|
```scala
|
|
75
75
|
Scope.global.scoped { scope =>
|
|
76
|
-
import scope
|
|
76
|
+
import scope._
|
|
77
77
|
val app = Resource.from[App](
|
|
78
78
|
Wire(Config("jdbc:postgres://localhost/db"))
|
|
79
79
|
).allocate
|
|
@@ -89,23 +89,27 @@ Scope.global.scoped { scope =>
|
|
|
89
89
|
|
|
90
90
|
## Installation
|
|
91
91
|
|
|
92
|
+
Add the following dependency to your `build.sbt`:
|
|
93
|
+
|
|
92
94
|
```scala
|
|
93
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "
|
|
95
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.31"
|
|
94
96
|
```
|
|
95
97
|
|
|
96
98
|
For cross-platform (Scala.js):
|
|
97
99
|
|
|
98
100
|
```scala
|
|
99
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-scope" % "
|
|
101
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-scope" % "0.0.31"
|
|
100
102
|
```
|
|
101
103
|
|
|
102
104
|
Supported Scala versions: 2.13.x and 3.x.
|
|
103
105
|
|
|
104
106
|
## Construction
|
|
105
107
|
|
|
108
|
+
`Wire` provides multiple ways to create instances, ranging from automatic macro-based derivation to manual construction for custom logic. Here are the main construction methods:
|
|
109
|
+
|
|
106
110
|
### Wire.shared[T] — derive a shared wire
|
|
107
111
|
|
|
108
|
-
The `Wire.shared[T]` macro inspects `T`'s primary constructor and generates a shared wire that reuses the same instance across dependents
|
|
112
|
+
The `Wire.shared[T]` macro inspects `T`'s primary constructor and generates a shared wire that reuses the same instance across dependents:
|
|
109
113
|
|
|
110
114
|
```scala
|
|
111
115
|
import zio.blocks.scope._
|
|
@@ -121,7 +125,7 @@ final class Database(config: Config) extends AutoCloseable {
|
|
|
121
125
|
val wire: Wire.Shared[Config, Database] = Wire.shared[Database]
|
|
122
126
|
|
|
123
127
|
Scope.global.scoped { scope =>
|
|
124
|
-
import scope
|
|
128
|
+
import scope._
|
|
125
129
|
val config = Config(debug = true)
|
|
126
130
|
val deps = Context[Config](config)
|
|
127
131
|
val db = allocate(wire.toResource(deps))
|
|
@@ -131,7 +135,7 @@ Scope.global.scoped { scope =>
|
|
|
131
135
|
|
|
132
136
|
### Wire.unique[T] — derive a unique wire
|
|
133
137
|
|
|
134
|
-
Like `shared[T]`, but creates a fresh instance each time the wire is used. Use for request-scoped or per-call services
|
|
138
|
+
Like `Wire.shared[T]`, but creates a fresh instance each time the wire is used. Use for request-scoped or per-call services:
|
|
135
139
|
|
|
136
140
|
```scala
|
|
137
141
|
import zio.blocks.scope._
|
|
@@ -144,7 +148,7 @@ final class RequestHandler {
|
|
|
144
148
|
val wire: Wire.Unique[Any, RequestHandler] = Wire.unique[RequestHandler]
|
|
145
149
|
|
|
146
150
|
Scope.global.scoped { scope =>
|
|
147
|
-
import scope
|
|
151
|
+
import scope._
|
|
148
152
|
val deps = Context.empty[Any]
|
|
149
153
|
val resource = wire.toResource(deps)
|
|
150
154
|
|
|
@@ -161,7 +165,7 @@ Scope.global.scoped { scope =>
|
|
|
161
165
|
|
|
162
166
|
### Wire.apply[T] — lift a pre-existing value
|
|
163
167
|
|
|
164
|
-
Creates a shared wire that injects a value you already have. If the value is `AutoCloseable`, its `close()` method is automatically registered as a finalizer
|
|
168
|
+
Creates a shared wire that injects a value you already have. If the value is `AutoCloseable`, its `close()` method is automatically registered as a finalizer:
|
|
165
169
|
|
|
166
170
|
```scala
|
|
167
171
|
import zio.blocks.scope._
|
|
@@ -172,7 +176,7 @@ val config = Config("jdbc:postgres://localhost/db")
|
|
|
172
176
|
val wire: Wire.Shared[Any, Config] = Wire(config)
|
|
173
177
|
|
|
174
178
|
Scope.global.scoped { scope =>
|
|
175
|
-
import scope
|
|
179
|
+
import scope._
|
|
176
180
|
val cfg = allocate(wire.toResource(Context.empty[Any]))
|
|
177
181
|
$(cfg)(_.dbUrl)
|
|
178
182
|
}
|
|
@@ -180,7 +184,7 @@ Scope.global.scoped { scope =>
|
|
|
180
184
|
|
|
181
185
|
### Wire.Shared.fromFunction — manual shared wire construction
|
|
182
186
|
|
|
183
|
-
Use this for custom construction logic when macro derivation doesn't fit
|
|
187
|
+
Use this for custom construction logic when macro derivation doesn't fit:
|
|
184
188
|
|
|
185
189
|
```scala
|
|
186
190
|
import zio.blocks.scope._
|
|
@@ -199,7 +203,7 @@ val wire: Wire.Shared[Config, Client] =
|
|
|
199
203
|
}
|
|
200
204
|
|
|
201
205
|
Scope.global.scoped { scope =>
|
|
202
|
-
import scope
|
|
206
|
+
import scope._
|
|
203
207
|
val config = Config(30)
|
|
204
208
|
val deps = Context[Config](config)
|
|
205
209
|
val client = allocate(wire.toResource(deps))
|
|
@@ -209,7 +213,7 @@ Scope.global.scoped { scope =>
|
|
|
209
213
|
|
|
210
214
|
### Wire.Unique.fromFunction — manual unique wire construction
|
|
211
215
|
|
|
212
|
-
Like `fromFunction`, but for unique wires
|
|
216
|
+
Like `Wire.Shared.fromFunction`, but for unique wires:
|
|
213
217
|
|
|
214
218
|
```scala
|
|
215
219
|
import zio.blocks.scope._
|
|
@@ -225,7 +229,7 @@ val wire: Wire.Unique[Any, RequestContext] =
|
|
|
225
229
|
}
|
|
226
230
|
|
|
227
231
|
Scope.global.scoped { scope =>
|
|
228
|
-
import scope
|
|
232
|
+
import scope._
|
|
229
233
|
val deps = Context.empty[Any]
|
|
230
234
|
val resource = wire.toResource(deps)
|
|
231
235
|
|
|
@@ -249,7 +253,7 @@ The fundamental difference is **reuse semantics**:
|
|
|
249
253
|
| **Instance reuse** | Same instance across entire dependency graph | New instance per allocation |
|
|
250
254
|
| **Finalization** | Runs when last referencing scope closes | Runs when each scope closes |
|
|
251
255
|
|
|
252
|
-
In the diamond pattern (where `App` depends on both `UserService` and `OrderService`, both of which depend on `Database`), a shared wire ensures `Database` is constructed once and both services receive the same instance
|
|
256
|
+
In the diamond pattern (where `App` depends on both `UserService` and `OrderService`, both of which depend on `Database`), a shared wire ensures `Database` is constructed once and both services receive the same instance:
|
|
253
257
|
|
|
254
258
|
```scala
|
|
255
259
|
import zio.blocks.scope._
|
|
@@ -281,7 +285,7 @@ val resource = Resource.from[App](
|
|
|
281
285
|
)
|
|
282
286
|
|
|
283
287
|
Scope.global.scoped { scope =>
|
|
284
|
-
import scope
|
|
288
|
+
import scope._
|
|
285
289
|
val app = resource.allocate
|
|
286
290
|
$(app)(_.check()) // true: Database is shared
|
|
287
291
|
}
|
|
@@ -289,10 +293,21 @@ Scope.global.scoped { scope =>
|
|
|
289
293
|
|
|
290
294
|
## Core Operations
|
|
291
295
|
|
|
296
|
+
The `Wire` interface provides a set of operations for inspecting and transforming wires:
|
|
297
|
+
|
|
292
298
|
### `Wire#isShared` and `Wire#isUnique`
|
|
293
299
|
|
|
294
300
|
Check the sharing strategy of a wire:
|
|
295
301
|
|
|
302
|
+
```scala
|
|
303
|
+
trait Wire[-In, +Out] {
|
|
304
|
+
def isShared: Boolean
|
|
305
|
+
def isUnique: Boolean = !isShared
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Here's how to use these methods:
|
|
310
|
+
|
|
296
311
|
```scala
|
|
297
312
|
import zio.blocks.scope._
|
|
298
313
|
|
|
@@ -309,6 +324,15 @@ println(s"uniqueWire.isUnique: ${uniqueWire.isUnique}") // true
|
|
|
309
324
|
|
|
310
325
|
Convert a wire to the opposite strategy:
|
|
311
326
|
|
|
327
|
+
```scala
|
|
328
|
+
trait Wire[-In, +Out] {
|
|
329
|
+
def shared: Wire.Shared[In, Out]
|
|
330
|
+
def unique: Wire.Unique[In, Out]
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Here's how to convert between sharing strategies:
|
|
335
|
+
|
|
312
336
|
```scala
|
|
313
337
|
import zio.blocks.scope._
|
|
314
338
|
|
|
@@ -329,6 +353,14 @@ Calling `shared` on an already-shared wire returns `this` (identity); likewise `
|
|
|
329
353
|
|
|
330
354
|
Converts the wire to a lazy `Resource` by providing the dependency context:
|
|
331
355
|
|
|
356
|
+
```scala
|
|
357
|
+
trait Wire[-In, +Out] {
|
|
358
|
+
def toResource(deps: Context[In]): Resource[Out]
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Here's how to use this method:
|
|
363
|
+
|
|
332
364
|
```scala
|
|
333
365
|
import zio.blocks.scope._
|
|
334
366
|
import zio.blocks.context.Context
|
|
@@ -341,7 +373,7 @@ val deps = Context[Config](Config("hello"))
|
|
|
341
373
|
val resource: Resource[Config] = wire.toResource(deps)
|
|
342
374
|
|
|
343
375
|
Scope.global.scoped { scope =>
|
|
344
|
-
import scope
|
|
376
|
+
import scope._
|
|
345
377
|
val cfg = allocate(resource)
|
|
346
378
|
$(cfg)(_.value)
|
|
347
379
|
}
|
|
@@ -349,7 +381,15 @@ Scope.global.scoped { scope =>
|
|
|
349
381
|
|
|
350
382
|
### `Wire#make` — construct directly from a wire
|
|
351
383
|
|
|
352
|
-
Directly construct a value without going through `Resource.toResource`. This is a low-level operation; prefer `allocate(wire.toResource(...))` for safety
|
|
384
|
+
Directly construct a value without going through `Resource.toResource`. This is a low-level operation; prefer `allocate(wire.toResource(...))` for safety:
|
|
385
|
+
|
|
386
|
+
```scala
|
|
387
|
+
trait Wire.Shared[-In, +Out] {
|
|
388
|
+
def make(scope: Scope, context: Context[In]): Out
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Here's how to use this method:
|
|
353
393
|
|
|
354
394
|
```scala
|
|
355
395
|
import zio.blocks.scope._
|
|
@@ -362,7 +402,7 @@ final class Service {
|
|
|
362
402
|
val wire = Wire.shared[Service]
|
|
363
403
|
|
|
364
404
|
Scope.global.scoped { scope =>
|
|
365
|
-
import scope
|
|
405
|
+
import scope._
|
|
366
406
|
val service = wire.asInstanceOf[Wire.Shared[Any, Service]].make(scope, Context.empty[Any])
|
|
367
407
|
println(service.getName)
|
|
368
408
|
}
|
|
@@ -384,11 +424,11 @@ import zio.blocks.scope._
|
|
|
384
424
|
|
|
385
425
|
final case class Config(dbUrl: String)
|
|
386
426
|
|
|
387
|
-
final class Logger(
|
|
427
|
+
final class Logger(implicit finalizer: Finalizer) {
|
|
388
428
|
def log(msg: String): Unit = println(msg)
|
|
389
429
|
}
|
|
390
430
|
|
|
391
|
-
final class Database(config: Config)(
|
|
431
|
+
final class Database(config: Config)(implicit scope: Scope) extends AutoCloseable {
|
|
392
432
|
def connect(): Unit = {
|
|
393
433
|
scope.defer(println("database connection closed"))
|
|
394
434
|
println(s"connecting to ${config.dbUrl}")
|
|
@@ -492,9 +532,25 @@ cd zio-blocks
|
|
|
492
532
|
|
|
493
533
|
**2. Run individual examples with sbt:**
|
|
494
534
|
|
|
495
|
-
|
|
535
|
+
Basic wire construction demonstrates how to create and use `Wire` for dependency injection. View the example source code:
|
|
496
536
|
|
|
497
537
|
```scala title="scope-examples/src/main/scala/wire/WireBasicExample.scala"
|
|
538
|
+
/*
|
|
539
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
540
|
+
*
|
|
541
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
542
|
+
* you may not use this file except in compliance with the License.
|
|
543
|
+
* You may obtain a copy of the License at
|
|
544
|
+
*
|
|
545
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
546
|
+
*
|
|
547
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
548
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
549
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
550
|
+
* See the License for the specific language governing permissions and
|
|
551
|
+
* limitations under the License.
|
|
552
|
+
*/
|
|
553
|
+
|
|
498
554
|
package wire
|
|
499
555
|
|
|
500
556
|
import zio.blocks.scope._
|
|
@@ -589,13 +645,31 @@ final class BasicApp(service: UserService) {
|
|
|
589
645
|
|
|
590
646
|
([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/wire/WireBasicExample.scala))
|
|
591
647
|
|
|
648
|
+
Run this example with:
|
|
649
|
+
|
|
592
650
|
```bash
|
|
593
|
-
sbt "scope-examples/runMain wire.
|
|
651
|
+
sbt "scope-examples/runMain wire.wireBasicExample"
|
|
594
652
|
```
|
|
595
653
|
|
|
596
|
-
|
|
654
|
+
Comparing shared vs unique semantics shows how shared wires reuse the same instance across dependents, while unique wires create fresh instances. View the example:
|
|
597
655
|
|
|
598
656
|
```scala title="scope-examples/src/main/scala/wire/WireSharedUniqueExample.scala"
|
|
657
|
+
/*
|
|
658
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
659
|
+
*
|
|
660
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
661
|
+
* you may not use this file except in compliance with the License.
|
|
662
|
+
* You may obtain a copy of the License at
|
|
663
|
+
*
|
|
664
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
665
|
+
*
|
|
666
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
667
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
668
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
669
|
+
* See the License for the specific language governing permissions and
|
|
670
|
+
* limitations under the License.
|
|
671
|
+
*/
|
|
672
|
+
|
|
599
673
|
package wire
|
|
600
674
|
|
|
601
675
|
import zio.blocks.scope._
|
|
@@ -692,13 +766,31 @@ final class UniqueDependencyApp(a: ServiceA, b: ServiceB) {
|
|
|
692
766
|
|
|
693
767
|
([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/wire/WireSharedUniqueExample.scala))
|
|
694
768
|
|
|
769
|
+
Run this example with:
|
|
770
|
+
|
|
695
771
|
```bash
|
|
696
|
-
sbt "scope-examples/runMain wire.
|
|
772
|
+
sbt "scope-examples/runMain wire.wireSharedUniqueExample"
|
|
697
773
|
```
|
|
698
774
|
|
|
699
|
-
|
|
775
|
+
Manual wire construction demonstrates how to use `fromFunction` for custom construction logic. View the example:
|
|
700
776
|
|
|
701
777
|
```scala title="scope-examples/src/main/scala/wire/WireFromFunctionExample.scala"
|
|
778
|
+
/*
|
|
779
|
+
* Copyright 2024-2026 John A. De Goes and the ZIO Contributors
|
|
780
|
+
*
|
|
781
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
782
|
+
* you may not use this file except in compliance with the License.
|
|
783
|
+
* You may obtain a copy of the License at
|
|
784
|
+
*
|
|
785
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
786
|
+
*
|
|
787
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
788
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
789
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
790
|
+
* See the License for the specific language governing permissions and
|
|
791
|
+
* limitations under the License.
|
|
792
|
+
*/
|
|
793
|
+
|
|
702
794
|
package wire
|
|
703
795
|
|
|
704
796
|
import zio.blocks.scope._
|
|
@@ -827,6 +919,8 @@ final class ManualWireApp(client: HttpClient, auth: Authenticator) {
|
|
|
827
919
|
|
|
828
920
|
([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/wire/WireFromFunctionExample.scala))
|
|
829
921
|
|
|
922
|
+
Run this example with:
|
|
923
|
+
|
|
830
924
|
```bash
|
|
831
|
-
sbt "scope-examples/runMain wire.
|
|
925
|
+
sbt "scope-examples/runMain wire.wireFromFunctionExample"
|
|
832
926
|
```
|
|
@@ -539,7 +539,6 @@ Path annotation (`atField`, `atIndex`, `atKey`, `atCase`) builds a [`DynamicOpti
|
|
|
539
539
|
```scala
|
|
540
540
|
import zio.blocks.schema.{DynamicOptic, DynamicValue, Schema, SchemaError}
|
|
541
541
|
|
|
542
|
-
implicit val intSchema: Schema[Int] = Schema[Int]
|
|
543
542
|
val data = DynamicValue.Sequence(DynamicValue.int(1), DynamicValue.int(2))
|
|
544
543
|
val optic = DynamicOptic.root.at(10)
|
|
545
544
|
|
|
@@ -39,13 +39,13 @@ The bidirectional data flow looks like this:
|
|
|
39
39
|
`As` is part of `zio-blocks-schema`:
|
|
40
40
|
|
|
41
41
|
```scala
|
|
42
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
42
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.31"
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
For Scala.js and Scala Native, use `%%%`:
|
|
46
46
|
|
|
47
47
|
```scala
|
|
48
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
48
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.31"
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Supported Scala versions: 2.13.x and 3.x.
|
|
@@ -240,7 +240,7 @@ trait As[A, B] {
|
|
|
240
240
|
|
|
241
241
|
```scala
|
|
242
242
|
val revAs: As[LongBox, IntBox] = boxAs.reverse
|
|
243
|
-
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@
|
|
243
|
+
// revAs: As[LongBox, IntBox] = zio.blocks.schema.As$$anon$1@70332401
|
|
244
244
|
|
|
245
245
|
revAs.into(LongBox(5L))
|
|
246
246
|
// res11: Either[SchemaError, IntBox] = Right(IntBox(5))
|
|
@@ -302,7 +302,7 @@ We import `As.reverseInto` and use it to obtain the reverse `Into[Int, String]`:
|
|
|
302
302
|
import As.reverseInto
|
|
303
303
|
|
|
304
304
|
val intToStr: Into[Int, String] = reverseInto[String, Int]
|
|
305
|
-
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$
|
|
305
|
+
// intToStr: Into[Int, String] = zio.blocks.schema.AsLowPriorityImplicits$$Lambda$17467/0x00007f7456961130@3a9535e3
|
|
306
306
|
intToStr.into(42)
|
|
307
307
|
// res14: Either[SchemaError, String] = Right("42")
|
|
308
308
|
```
|
|
@@ -69,13 +69,13 @@ Compare this to a manual implementation:
|
|
|
69
69
|
`Into` is part of the `zio-blocks-schema` module:
|
|
70
70
|
|
|
71
71
|
```scala
|
|
72
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
72
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.31"
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
For Scala.js:
|
|
76
76
|
|
|
77
77
|
```scala
|
|
78
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
78
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.31"
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/schema-expr.md
CHANGED
|
@@ -70,13 +70,13 @@ val result: Either[OpticCheck, Seq[Boolean]] = combined.eval(alice)
|
|
|
70
70
|
## Installation
|
|
71
71
|
|
|
72
72
|
```scala
|
|
73
|
-
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.
|
|
73
|
+
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.31"
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
For cross-platform (Scala.js):
|
|
77
77
|
|
|
78
78
|
```scala
|
|
79
|
-
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.
|
|
79
|
+
libraryDependencies += "dev.zio" %%% "zio-blocks-schema" % "0.0.31"
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
Supported Scala versions: 2.13.x and 3.x.
|
package/reference/schema.md
CHANGED
|
@@ -173,8 +173,6 @@ ZIO Blocks provides specialized `Option` schemas optimized for primitive types.
|
|
|
173
173
|
```scala
|
|
174
174
|
import zio.blocks.schema.Schema
|
|
175
175
|
|
|
176
|
-
import zio.blocks.schema.Schema
|
|
177
|
-
|
|
178
176
|
// Specialized primitive options (no boxing overhead)
|
|
179
177
|
Schema[Option[Boolean]] // Also: Byte, Short, Int, Long, Float, Double, Char, Unit
|
|
180
178
|
```
|
|
@@ -193,12 +191,19 @@ Schema[Option[A]] // Generic option for reference types
|
|
|
193
191
|
ZIO Blocks also provides polymorphic schemas for standard Scala collections. You can summon schemas for collections of any element type `A` (and key/value types `K`/`V` for maps):
|
|
194
192
|
|
|
195
193
|
```scala
|
|
194
|
+
import zio.blocks.schema.Schema
|
|
195
|
+
|
|
196
|
+
// Built-in Scala collections
|
|
196
197
|
Schema[List[A]] // Immutable singly-linked list
|
|
197
198
|
Schema[Vector[A]] // Immutable indexed sequence (efficient random access)
|
|
198
199
|
Schema[Set[A]] // Immutable set (unique elements)
|
|
199
200
|
Schema[Seq[A]] // General immutable sequence
|
|
200
201
|
Schema[IndexedSeq[A]] // Indexed sequence
|
|
201
202
|
Schema[Map[K, V]] // Immutable key-value mapping
|
|
203
|
+
|
|
204
|
+
// ZIO Blocks collections (the chunk module)
|
|
205
|
+
Schema[zio.blocks.chunk.Chunk[A]] // Chunk sequence
|
|
206
|
+
Schema[zio.blocks.chunk.ChunkMap[K, V]] // ChunkMap key-value mapping
|
|
202
207
|
```
|
|
203
208
|
|
|
204
209
|
To learn how to create custom collection schemas, check out the [`Sequence`](./reflect.md#3-sequence) and [`Map`](./reflect.md#4-map) nodes on the documentation of [`Reflect`](./reflect.md) data type.
|
|
@@ -358,13 +363,13 @@ In the following example, we derive a JSON codec for the `Person` case class usi
|
|
|
358
363
|
|
|
359
364
|
```scala
|
|
360
365
|
import zio.blocks.schema.Schema
|
|
361
|
-
import zio.blocks.schema.json.{JsonFormat,
|
|
366
|
+
import zio.blocks.schema.json.{JsonFormat, JsonCodec}
|
|
362
367
|
|
|
363
368
|
case class Person(name: String, age: Int)
|
|
364
369
|
|
|
365
370
|
object Person {
|
|
366
371
|
implicit val schema: Schema[Person] = Schema.derived
|
|
367
|
-
val codec:
|
|
372
|
+
val codec: JsonCodec[Person] = schema.derive(JsonFormat)
|
|
368
373
|
}
|
|
369
374
|
|
|
370
375
|
val person = Person("John", 42)
|