@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.
Files changed (36) hide show
  1. package/guides/compile-time-resource-safety-with-scope.md +997 -0
  2. package/guides/query-dsl-extending.md +1 -1
  3. package/guides/query-dsl-fluent-builder.md +203 -203
  4. package/guides/query-dsl-reified-optics.md +1 -1
  5. package/guides/query-dsl-sql.md +1 -1
  6. package/guides/zio-schema-migration.md +8 -8
  7. package/index.md +20 -14
  8. package/package.json +1 -1
  9. package/reference/codec.md +17 -17
  10. package/reference/context.md +49 -1
  11. package/reference/docs.md +1 -1
  12. package/reference/dynamic-schema.md +39 -42
  13. package/reference/formats.md +5 -11
  14. package/reference/json-schema.md +2 -2
  15. package/reference/json.md +34 -43
  16. package/reference/media-type.md +2 -2
  17. package/reference/modifier.md +4 -4
  18. package/reference/resource-management/defer-handle.md +246 -0
  19. package/reference/resource-management/finalization.md +286 -0
  20. package/reference/resource-management/finalizer.md +167 -0
  21. package/reference/{resource-management-di → resource-management}/index.md +3 -8
  22. package/reference/{resource-management-di → resource-management}/resource.md +532 -50
  23. package/reference/resource-management/scope.md +3026 -0
  24. package/reference/resource-management/unscoped.md +125 -0
  25. package/reference/{resource-management-di → resource-management}/wire.md +122 -28
  26. package/reference/schema-error.md +0 -1
  27. package/reference/schema-evolution/as.md +4 -4
  28. package/reference/schema-evolution/into.md +2 -2
  29. package/reference/schema-expr.md +2 -2
  30. package/reference/schema.md +9 -4
  31. package/reference/streams.md +989 -0
  32. package/reference/type-class-derivation.md +14 -15
  33. package/ringbuffer.md +1 -1
  34. package/sidebars.js +9 -4
  35. package/undocumented-report.md +1 -1
  36. package/reference/resource-management-di/scope.md +0 -1423
@@ -36,24 +36,282 @@ Without Resources, managing complex initialization and cleanup is tedious and er
36
36
  Add the ZIO Blocks Scope module to your `build.sbt`:
37
37
 
38
38
  ```scala
39
- libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.29"
39
+ libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.31"
40
40
  ```
41
41
 
42
42
  For cross-platform (Scala.js):
43
43
 
44
44
  ```scala
45
- libraryDependencies += "dev.zio" %%% "zio-blocks-scope" % "0.0.29"
45
+ libraryDependencies += "dev.zio" %%% "zio-blocks-scope" % "0.0.31"
46
46
  ```
47
47
 
48
48
  Supported Scala versions: 2.13.x and 3.x.
49
49
 
50
+ ## Shared and Unique Resources
51
+
52
+ Resource offers two distinct types for managing instance lifecycles: **Shared** for singleton-like resources that are allocated once and reused across multiple scopes, and **Unique** for fresh resources created on each allocation. Understanding when to use each type is critical for building efficient and correct resource-management architectures.
53
+
54
+ ### Comparison
55
+
56
+ | Aspect | Shared | Unique |
57
+ |--------------------|----------------------------------------------------|-------------------------------------------|
58
+ | **Creation** | `Resource.shared(f)` | `Resource.unique(f)` or `Resource(value)` |
59
+ | **Memoization** | Yes, with reference counting | No, fresh per allocation |
60
+ | **When to use** | Expensive resources (DB connections, thread pools) | Per-request state, isolated handlers |
61
+ | **Instance reuse** | Same instance across all allocations | New instance per allocation |
62
+ | **Finalization** | Runs when last reference released | Runs immediately when scope closes |
63
+ | **Thread safety** | Lock-free atomic reference counting | Per-scope, no global coordination needed |
64
+
65
+ ### Shared Resources
66
+
67
+ **Shared resources are memoized**: the first allocation initializes the instance; subsequent allocations return the same reference with automatic reference counting. Finalizers run only when the last reference is released. Use shared resources for **expensive, globally singular components** — database connection pools, thread pools, logging systems, caches, and other heavyweight infrastructure that should exist exactly once for the lifetime of the application.
68
+
69
+ The canonical example is a **database connection pool**. Building a fresh pool for each service layer is wasteful and defeats pooling's purpose. Instead, wrap the pool in a shared resource: both the user service and order service allocate the same pool instance, with the system automatically tracking references and closing the pool only when the last service releases it.
70
+
71
+ Here's what shared acquisition looks like:
72
+
73
+ ```scala
74
+ object Resource {
75
+ def shared[A](f: Scope => A): Resource[A]
76
+ }
77
+ ```
78
+
79
+ When you allocate a shared resource, reference counting ensures cleanup happens exactly once:
80
+
81
+ ```scala
82
+ import zio.blocks.scope._
83
+
84
+ class DatabasePool extends AutoCloseable {
85
+ def close(): Unit = println("Pool closed (after all services released)")
86
+ }
87
+
88
+ val poolResource = Resource.shared { scope =>
89
+ println("Creating database pool (first allocation only)")
90
+ new DatabasePool
91
+ }
92
+
93
+ // Allocate in ServiceA
94
+ Scope.global.scoped { scopeA =>
95
+ import scopeA._
96
+ val poolA: $[DatabasePool] = poolResource.allocate
97
+ println("ServiceA allocated pool")
98
+
99
+ // Allocate in ServiceB within a nested scope
100
+ scopeA.scoped { scopeB =>
101
+ import scopeB._
102
+ val poolB: $[DatabasePool] = poolResource.allocate
103
+ println("ServiceB allocated same pool instance (reference count incremented)")
104
+
105
+ // Both services share the same underlying pool instance
106
+ println("Both ServiceA and ServiceB are using the same pool instance")
107
+ }
108
+ println("ServiceB released, but pool stays open for ServiceA (ref count -= 1)")
109
+ }
110
+ println("All services released, pool closed (ref count == 0)")
111
+ ```
112
+
113
+ ### Unique Resources
114
+
115
+ **Unique resources create fresh instances each time**: every allocation produces a new value. Finalizers run when their owning scope closes, independent of other allocations. Use unique resources for **per-request state, isolated services, and stateful handlers** — anything that should never be shared because it encapsulates request-specific or scope-specific data.
116
+
117
+ A typical scenario is **per-request caches**: each API request gets its own cache instance to avoid one request polluting another's cached data. Similarly, stateful handlers (parser state machines, transaction contexts, event buffers) need isolation to prevent cross-contamination.
118
+
119
+ Here's what unique acquisition looks like:
120
+
121
+ ```scala
122
+ object Resource {
123
+ def unique[A](f: Scope => A): Resource[A]
124
+ }
125
+ ```
126
+
127
+ When you allocate a unique resource, each allocation is independent:
128
+
129
+ ```scala
130
+ import zio.blocks.scope._
131
+
132
+ class RequestCache extends AutoCloseable {
133
+ def close(): Unit = println("Request cache closed")
134
+ }
135
+
136
+ val cacheResource = Resource.unique { scope =>
137
+ println("Creating new request cache")
138
+ new RequestCache
139
+ }
140
+
141
+ // Two allocations in the same scope produce different instances
142
+ Scope.global.scoped { scope =>
143
+ import scope._
144
+ println("Creating first cache...")
145
+ val cache1: $[RequestCache] = cacheResource.allocate
146
+
147
+ println("Creating second cache...")
148
+ val cache2: $[RequestCache] = cacheResource.allocate
149
+
150
+ println("Both caches are independent instances")
151
+ }
152
+ ```
153
+
154
+ ### Diamond Dependency Pattern
155
+
156
+ A classic architecture uses shared resources to solve **diamond dependencies**, where multiple services depend on the same expensive component. Consider an e-commerce application where both `ProductService` and `OrderService` depend on `Logger`:
157
+
158
+ ```
159
+ CachingApp
160
+ / \
161
+ / \
162
+ ProductService OrderService
163
+ \ /
164
+ \ /
165
+ Logger
166
+ ```
167
+
168
+ Without shared resources, each service would receive a different `Logger` instance. With `Resource.shared`, both services automatically receive the same singleton instance:
169
+
170
+ ```scala
171
+ import zio.blocks.scope._
172
+
173
+ class Logger extends AutoCloseable {
174
+ def log(msg: String): Unit = println(s"LOG: $msg")
175
+ def close(): Unit = println("Logger closed")
176
+ }
177
+
178
+ class ProductService(val logger: Logger) {
179
+ def findProduct(id: String): String = {
180
+ logger.log(s"Finding product $id")
181
+ s"Product-$id"
182
+ }
183
+ }
184
+
185
+ class OrderService(val logger: Logger) {
186
+ def createOrder(productId: String): String = {
187
+ logger.log(s"Creating order for $productId")
188
+ s"ORD-123"
189
+ }
190
+ }
191
+
192
+ class CachingApp(val productService: ProductService, val orderService: OrderService) extends AutoCloseable {
193
+ def close(): Unit = println("App closed")
194
+ }
195
+
196
+ Scope.global.scoped { scope =>
197
+ import scope._
198
+
199
+ // Use Wire.shared[Logger] to ensure only one instance is created
200
+ val app: $[CachingApp] = allocate(
201
+ Resource.from[CachingApp](
202
+ Wire.shared[Logger] // Both services get the same Logger instance
203
+ )
204
+ )
205
+
206
+ $(app) { a =>
207
+ println(s"ProductService and OrderService share Logger? ${a.productService.logger eq a.orderService.logger}")
208
+ a.productService.findProduct("P001")
209
+ a.orderService.createOrder("P001")
210
+ }
211
+ }
212
+ ```
213
+
214
+ In this pattern, `Resource.shared` (via `Wire.shared`) guarantees a single `Logger` instance across all services, eliminating duplication while maintaining proper cleanup semantics.
215
+
216
+ ### Multiple Shared Dependencies
217
+
218
+ Most applications need more than one shared resource. Here's a realistic example where `CachingApp` depends on both a shared `Logger` and a shared `MetricsCollector`. Both resources are singletons that all services share:
219
+
220
+ ```
221
+ CachingApp
222
+ / \
223
+ / \
224
+ / \
225
+ ProductService OrderService
226
+ / \ / \
227
+ / \ / \
228
+ Logger Metrics Logger Metrics
229
+ \ / \ /
230
+ \ / \ /
231
+ \/ \/
232
+ (shared singleton instances)
233
+ ```
234
+
235
+ Here's the implementation:
236
+
237
+ ```scala
238
+ import zio.blocks.scope._
239
+
240
+ class Logger extends AutoCloseable {
241
+ def log(msg: String): Unit = println(s"[LOG] $msg")
242
+ def close(): Unit = println("[Logger] Closed")
243
+ }
244
+
245
+ class MetricsCollector extends AutoCloseable {
246
+ private var eventCount = 0
247
+ def recordEvent(name: String): Unit = {
248
+ eventCount += 1
249
+ println(s"[METRICS] Event: $name (total: $eventCount)")
250
+ }
251
+ def close(): Unit = println(s"[MetricsCollector] Closed after $eventCount events")
252
+ }
253
+
254
+ class ProductService(val logger: Logger, val metrics: MetricsCollector) {
255
+ def findProduct(id: String): String = {
256
+ logger.log(s"Finding product $id")
257
+ metrics.recordEvent("product.find")
258
+ s"Product-$id"
259
+ }
260
+ }
261
+
262
+ class OrderService(val logger: Logger, val metrics: MetricsCollector) {
263
+ def createOrder(productId: String): String = {
264
+ logger.log(s"Creating order for $productId")
265
+ metrics.recordEvent("order.create")
266
+ s"ORD-123"
267
+ }
268
+ }
269
+
270
+ class CachingApp(
271
+ val productService: ProductService,
272
+ val orderService: OrderService
273
+ ) extends AutoCloseable {
274
+ def close(): Unit = println("[CachingApp] Closed")
275
+ }
276
+
277
+ Scope.global.scoped { scope =>
278
+ import scope._
279
+
280
+ // Both Logger and MetricsCollector are shared (singleton instances)
281
+ val app: $[CachingApp] = allocate(
282
+ Resource.from[CachingApp](
283
+ Wire.shared[Logger], // One Logger for all services
284
+ Wire.shared[MetricsCollector] // One MetricsCollector for all services
285
+ )
286
+ )
287
+
288
+ $(app) { a =>
289
+ println(s"ProductService and OrderService share Logger? ${a.productService.logger eq a.orderService.logger}")
290
+ println(s"ProductService and OrderService share Metrics? ${a.productService.metrics eq a.orderService.metrics}")
291
+
292
+ a.productService.findProduct("P001")
293
+ a.orderService.createOrder("P001")
294
+ }
295
+ }
296
+ ```
297
+
298
+ When this scope closes, both the `Logger` and `MetricsCollector` are finalized exactly once, regardless of how many services reference them. The macro automatically builds a dependency graph, verifies all wires, and ensures LIFO cleanup order.
299
+
50
300
  ## Construction
51
301
 
52
- Resources can be created in several ways: from values, from explicit acquire/release pairs, from `AutoCloseable` types, or from custom functions.
302
+ Resources can be created in several ways: from values, from explicit acquire/release pairs, from `AutoCloseable` types, from custom functions, or derived automatically from a type's constructor using the `Resource.from` macros.
53
303
 
54
- ### `Resource.apply` — wrap a value
304
+ ### `Resource.apply` — Wrap a Value
55
305
 
56
- Wraps a by-name value as a resource. If the value implements `AutoCloseable`, its `close()` method is automatically registered as a finalizer.
306
+ Wraps a by-name value as a resource. If the value implements `AutoCloseable`, its `close()` method is automatically registered as a finalizer:
307
+
308
+ ```scala
309
+ object Resource {
310
+ def apply[A](value: => A): Resource[A]
311
+ }
312
+ ```
313
+
314
+ Here's how to use it:
57
315
 
58
316
  ```scala
59
317
  import zio.blocks.scope._
@@ -68,10 +326,18 @@ val configResource = Resource(Config(debug = true))
68
326
  val dbResource = Resource(new Database("mydb"))
69
327
  ```
70
328
 
71
- ### `Resource.acquireRelease` — explicit lifecycle
329
+ ### `Resource.acquireRelease` — Explicit Lifecycle
72
330
 
73
331
  Creates a resource with separate acquire and release functions. The acquire thunk runs during allocation; the release function is registered as a finalizer:
74
332
 
333
+ ```scala
334
+ object Resource {
335
+ def acquireRelease[A](acquire: => A)(release: A => Unit): Resource[A]
336
+ }
337
+ ```
338
+
339
+ Here's an example with file handling:
340
+
75
341
  ```scala
76
342
  import zio.blocks.scope._
77
343
  import java.io.FileInputStream
@@ -83,10 +349,18 @@ val fileResource = Resource.acquireRelease {
83
349
  }
84
350
  ```
85
351
 
86
- ### `Resource.fromAutoCloseable` — type-safe wrapping
352
+ ### `Resource.fromAutoCloseable` — Type-Safe Wrapping
87
353
 
88
354
  Creates a resource specifically for `AutoCloseable` subtypes. This is a compile-time verified alternative to `Resource(value)` when you know the value is closeable:
89
355
 
356
+ ```scala
357
+ object Resource {
358
+ def fromAutoCloseable[A <: AutoCloseable](value: => A): Resource[A]
359
+ }
360
+ ```
361
+
362
+ Here's an example with a stream:
363
+
90
364
  ```scala
91
365
  import zio.blocks.scope._
92
366
  import java.io.BufferedInputStream
@@ -97,25 +371,70 @@ val streamResource = Resource.fromAutoCloseable {
97
371
  }
98
372
  ```
99
373
 
100
- ### `Resource.shared` — memoized with reference counting
374
+ ### `Resource.shared` — Memoized with Reference Counting
375
+
376
+ Creates a shared resource that memoizes its value across multiple allocations using reference counting. The first allocation initializes the value using an `OpenScope` parented to `Scope.global`; subsequent allocations increment the reference count and return the same instance. Each scope that receives the shared value registers a finalizer that decrements the count. When the count reaches zero, the shared scope closes automatically. This mechanism is **thread-safe and lock-free**, implemented using `AtomicReference` with atomic compare-and-swap operations to avoid contention.
377
+
378
+ Under the hood, a shared resource progresses through four states: (1) **Uninitialized** — the resource has never been allocated and the first call triggers initialization; (2) **Pending** — initialization is in progress and other threads wait via spin-yield for the value to become available; (3) **Created** — the value is fully initialized and ready, each allocation increments the reference count, and each scope registers a finalizer to decrement it; (4) **Destroyed** — the reference count reached zero, the resource was cleaned up, and further allocations will fail. This state machine ensures that no matter how many threads try to allocate simultaneously, the underlying value initializes exactly once, and cleanup happens exactly once when the last reference is released.
101
379
 
102
- Creates a shared resource that memoizes its value across multiple allocations. The first call initializes the value; subsequent calls return the same instance with reference counting. Finalizers run only when the last reference is released:
380
+ Use shared resources for **expensive, singleton-like components** (database connection pools, thread pools, logging systems, caches) that should exist exactly once for the lifetime of the application, even when multiple services depend on them.
381
+
382
+ Here's the signature:
383
+
384
+ ```scala
385
+ object Resource {
386
+ def shared[A](f: Scope => A): Resource[A]
387
+ }
388
+ ```
389
+
390
+ Here's a realistic example showing reference counting and automatic cleanup:
103
391
 
104
392
  ```scala
105
393
  import zio.blocks.scope._
106
394
 
107
- var initCount = 0
395
+ class ExpensiveComponent extends AutoCloseable {
396
+ println("ExpensiveComponent initialized (expensive operation)")
397
+ def close(): Unit = println("ExpensiveComponent cleaned up (last reference released)")
398
+ }
399
+
400
+ val sharedResource = Resource.shared { scope =>
401
+ new ExpensiveComponent()
402
+ }
403
+
404
+ // Multiple services allocating the same shared resource
405
+ Scope.global.scoped { scope =>
406
+ import scope._
407
+
408
+ // First allocation initializes the component
409
+ println("ServiceA allocating...")
410
+ val componentA: $[ExpensiveComponent] = sharedResource.allocate
108
411
 
109
- val sharedResource = Resource.shared[Int] { _ =>
110
- initCount += 1
111
- initCount
412
+ // ServiceB in a nested scope receives the same instance
413
+ scope.scoped { innerScope =>
414
+ import innerScope._
415
+ println("ServiceB allocating (same instance, ref count += 1)...")
416
+ val componentB: $[ExpensiveComponent] = sharedResource.allocate
417
+ println("Both services have the same instance")
418
+ }
419
+
420
+ println("ServiceB released, but component stays alive (ref count -= 1)")
112
421
  }
422
+
423
+ println("ServiceA released, component cleaned up (ref count == 0)")
113
424
  ```
114
425
 
115
- ### `Resource.unique` — fresh instances
426
+ ### `Resource.unique` — Fresh Instances
116
427
 
117
428
  Creates a unique resource that produces a fresh instance each time it's allocated. Use for per-request state or resources that should never be shared:
118
429
 
430
+ ```scala
431
+ object Resource {
432
+ def unique[A](f: Scope => A): Resource[A]
433
+ }
434
+ ```
435
+
436
+ Here's an example showing fresh instances:
437
+
119
438
  ```scala
120
439
  import zio.blocks.scope._
121
440
 
@@ -127,13 +446,92 @@ val uniqueResource = Resource.unique[Int] { _ =>
127
446
  }
128
447
  ```
129
448
 
449
+ ### `Resource.from[T]` — Derive from Constructor
450
+
451
+ Derives a `Resource[T]` from `T`'s primary constructor, requiring no external dependencies.
452
+ If `T` extends `AutoCloseable`, `Resource.from` automatically registers its `close()` method
453
+ as a finalizer:
454
+
455
+ ```scala
456
+ object Resource {
457
+ def from[T]: Resource[T]
458
+ }
459
+ ```
460
+
461
+ To derive a resource for a type that has no constructor dependencies:
462
+
463
+ ```scala
464
+ import zio.blocks.scope._
465
+
466
+ class MetricsCollector extends AutoCloseable {
467
+ def record(event: String): Unit = println(s"Recording: $event")
468
+ def close(): Unit = println("MetricsCollector closed")
469
+ }
470
+
471
+ val metricsResource = Resource.from[MetricsCollector]
472
+ ```
473
+
474
+ Internally, the macro inspects `T`'s constructor at compile time, verifies that no external
475
+ dependencies are needed, and synthesizes a `Resource.shared` that instantiates `T` and —
476
+ when `T` extends `AutoCloseable` — registers `close()` as a scope finalizer. Any attempt
477
+ to use `Resource.from[T]` on a type with unsatisfied constructor parameters results in a
478
+ compile-time error.
479
+
480
+ ### `Resource.from[T](wires: Wire[?, ?]*)` — Derive with Dependency Overrides
481
+
482
+ Derives a `Resource[T]` from `T`'s constructor, with `Wire` values provided as dependency
483
+ overrides. Any dependency not covered by an explicit wire is auto-derived if the macro can
484
+ construct it; otherwise a compile-time error is produced:
485
+
486
+ ```scala
487
+ object Resource {
488
+ def from[T](wires: Wire[?, ?]*): Resource[T]
489
+ }
490
+ ```
491
+
492
+ To derive a resource for a type whose dependencies are provided via wires:
493
+
494
+ ```scala
495
+ import zio.blocks.scope._
496
+
497
+ case class Config(host: String, port: Int)
498
+
499
+ class Logger(config: Config) extends AutoCloseable {
500
+ def log(msg: String): Unit = println(s"[$msg] ${config.host}:${config.port}")
501
+ def close(): Unit = log("Logger shutting down")
502
+ }
503
+
504
+ class Service(logger: Logger) extends AutoCloseable {
505
+ def run(): Unit = logger.log("Service running")
506
+ def close(): Unit = logger.log("Service shutting down")
507
+ }
508
+
509
+ val serviceResource = Resource.from[Service](
510
+ Wire(Config("localhost", 8080))
511
+ )
512
+ ```
513
+
514
+ Internally, the macro builds a complete dependency graph at compile time: it inspects each
515
+ provided wire's input and output types, identifies all constructor parameters of `T`, and
516
+ auto-derives wires for any remaining dependencies. It then topologically sorts all wires to
517
+ determine acquisition order and composes them into a single `Resource` whose finalizers run
518
+ in LIFO order — inner dependencies close before outer ones.
519
+
130
520
  ## Core Operations
131
521
 
132
522
  Resources support transformation and composition through `map`, `flatMap`, and `zip`.
133
523
 
134
- ### `Resource#map` — transform the value
524
+ ### `Resource#map` — Transform the Value
135
525
 
136
- Transforms the value produced by a resource without affecting finalization. The transformation function is applied after the resource is acquired.
526
+ Transforms the value produced by a resource without affecting finalization. The transformation function is applied after the resource is acquired:
527
+
528
+ ```scala
529
+ trait Resource[+A] {
530
+ def map[B](f: A => B): Resource[B]
531
+ }
532
+ ```
533
+
534
+ Here's a usage example:
137
535
 
138
536
  ```scala
139
537
  import zio.blocks.scope._
@@ -142,10 +540,18 @@ val portResource = Resource(8080)
142
540
  val urlResource = portResource.map(port => s"http://localhost:$port")
143
541
  ```
144
542
 
145
- ### `Resource#flatMap` — sequence resources
543
+ ### `Resource#flatMap` — Sequence Resources
146
544
 
147
545
  Sequences two resources, using the result of the first to create the second. Both sets of finalizers are registered and run in LIFO order (inner before outer):
148
546
 
547
+ ```scala
548
+ trait Resource[+A] {
549
+ def flatMap[B](f: A => Resource[B]): Resource[B]
550
+ }
551
+ ```
552
+
553
+ Here's an example with dependent resources:
554
+
149
555
  ```scala
150
556
  import zio.blocks.scope._
151
557
 
@@ -162,10 +568,18 @@ val dbResource = configResource.flatMap { config =>
162
568
  }
163
569
  ```
164
570
 
165
- ### `Resource#zip` — combine resources
571
+ ### `Resource#zip` — Combine Resources
166
572
 
167
573
  Combines two resources into a single resource that produces a tuple of both values. Both resources are acquired and both sets of finalizers are registered:
168
574
 
575
+ ```scala
576
+ trait Resource[+A] {
577
+ def zip[B](that: Resource[B]): Resource[(A, B)]
578
+ }
579
+ ```
580
+
581
+ Here's an example combining multiple resources:
582
+
169
583
  ```scala
170
584
  import zio.blocks.scope._
171
585
 
@@ -184,44 +598,90 @@ val cacheResource = Resource.fromAutoCloseable(new Cache())
184
598
  val combined = dbResource.zip(cacheResource)
185
599
  ```
186
600
 
187
- ## Shared vs. Unique
601
+ ### `Resource#allocate` — Acquire Within a Scope
602
+
603
+ Allocates a `Resource[A]` within the current `Scope`, returning a scoped `$[A]`. This is syntax sugar for `scope.allocate(resource)`, available via `import scope._` inside a `scoped` block:
604
+
605
+ ```scala
606
+ implicit class ResourceOps[A](private val r: Resource[A]) {
607
+ def allocate: $[A]
608
+ }
609
+ ```
610
+
611
+ To allocate a resource and use its value inside a scope:
612
+
613
+ ```scala
614
+ import zio.blocks.scope._
615
+
616
+ class Database extends AutoCloseable {
617
+ def query(sql: String): String = s"Result: $sql"
618
+ def close(): Unit = println("Database closed")
619
+ }
620
+
621
+ Scope.global.scoped { implicit scope =>
622
+ import scope._
623
+ val db: $[Database] = Resource.fromAutoCloseable(new Database).allocate
624
+ $(db)(_.query("SELECT 1"))
625
+ }
626
+ ```
627
+
628
+ ### `$[Resource[A]]#allocate` — Allocate a Scoped Resource
188
629
 
189
- The fundamental difference is **reuse semantics**:
630
+ Allocates a `$[Resource[A]]` — a Resource that is itself a scoped value — returning `$[A]`. Use this when a method on a scoped object returns a Resource and you need to immediately acquire it while keeping the result scoped.
190
631
 
191
- | Aspect | Shared | Unique |
192
- |-------------------|-----------------------------------|----------------------------------------------|
193
- | **Creation** | `Resource.shared(f)` | `Resource.unique(f)` or `Resource(value)` |
194
- | **Memoization** | Yes, with reference counting | No, fresh per allocation |
195
- | **When to use** | Expensive resources (DB connections, thread pools) | Per-request state, stateful handlers |
196
- | **Instance reuse** | Same instance across nested scopes | New instance per allocation |
197
- | **Finalization** | Runs when last reference released | Runs when scope closes |
632
+ **What is a scoped resource?** A scoped value `$[A]` represents an `A` that is valid only while a scope is alive. A **scoped resource** `$[Resource[A]]` is a Resource object that exists *inside* that scope. When you call a method like `$(pool)(_.lease())` that returns a Resource, the result is typed as `$[Resource[Connection]]` — the Resource itself is scoped. The `.allocate` extension method unwraps this scoped resource and acquires it, returning the acquired value as a new scoped value `$[A]`.
198
633
 
199
- In a diamond dependency pattern (where `AppService` depends on both `UserService` and `OrderService`, both depending on `Database`), using `Resource.shared[Database]` ensures both services receive the same instance.
634
+ **Motivation:** This pattern appears frequently in resource factories. A scoped object (like a database pool) has methods that produce new Resources. Without this extension, you'd need to unwrap the `$[Resource[A]]` from the `$` context, allocate it separately, and re-wrap the result. The `.allocate` method chains naturally, letting you write `$(pool)(_.lease()).allocate` instead of dealing with intermediate unwrapping.
200
635
 
201
- ## Integration with Wire and Scope
636
+ The implicit class:
637
+
638
+ ```scala
639
+ implicit class ScopedResourceOps[A](private val sr: $[Resource[A]]) {
640
+ def allocate: $[A]
641
+ }
642
+ ```
202
643
 
203
- `Resource` is the foundation of ZIO Blocks' dependency injection. `Wire` describes how to build a service; `Resource` describes how to manage its lifecycle. When used together with the `Resource.from[T]` macro, they enable compile-safe automatic dependency injection:
644
+ Here's a realistic example where a database pool (a scoped object) has a method that returns a Resource for individual connections:
204
645
 
205
646
  ```scala
206
647
  import zio.blocks.scope._
207
648
 
208
- case class Config(debug: Boolean)
649
+ class Connection extends AutoCloseable {
650
+ def query(sql: String): String = s"Result: $sql"
651
+ def close(): Unit = println("Connection closed")
652
+ }
209
653
 
210
- class Logger(config: Config) {
211
- def log(msg: String): Unit = println(s"[${config.debug}] $msg")
654
+ class Pool extends AutoCloseable {
655
+ def lease(): Resource[Connection] = Resource.fromAutoCloseable(new Connection)
656
+ def close(): Unit = println("Pool closed")
212
657
  }
213
658
 
214
- class Service(logger: Logger) extends AutoCloseable {
215
- def run(): Unit = logger.log("Running")
216
- def close(): Unit = logger.log("Shutting down")
659
+ Scope.global.scoped { implicit scope =>
660
+ import scope._
661
+ val pool: $[Pool] = Resource.fromAutoCloseable(new Pool).allocate
662
+ val conn: $[Connection] = $(pool)(_.lease()).allocate
663
+ $(conn)(_.query("SELECT 1"))
217
664
  }
665
+ ```
666
+
667
+
668
+ ## Integration
669
+
670
+ Resource is a core abstraction in ZIO Blocks' resource management ecosystem:
218
671
 
672
+ - **[`Scope`](./scope.md)** — Resources require a `Scope` for allocation. The `Scope` manages the lifetime of acquired resources and automatically runs finalizers when the scope closes.
673
+ - **[`Wire`](./wire.md)** — The `Resource.from[T](wires)` macro builds dependency graphs using `Wire` values, enabling constructor-based dependency injection with automatic resource management.
674
+ - **[`Finalizer`](./finalizer.md)** — Resources register their cleanup logic with `Finalizer` objects, which execute in LIFO order when scopes close.
675
+
676
+ For example, `Resource.from[T]` uses `Wire` to construct instances with their dependencies, automatically registering any `AutoCloseable` cleanup:
677
+
678
+ ```scala
219
679
  val serviceResource = Resource.from[Service](
220
- Wire(Config(debug = true))
680
+ Wire(Config("localhost", 8080))
221
681
  )
222
682
  ```
223
683
 
224
- See [`Wire`](./wire.md) for how to declare dependency recipes and [`Scope`](./scope.md) for scope-based resource management.
684
+ This builds a complete dependency graph: `Config` → `Logger` → `Service`, with all finalizers managed by the containing `Scope`.
225
685
 
226
686
  ## Running the Examples
227
687
 
@@ -236,7 +696,9 @@ cd zio-blocks
236
696
 
237
697
  **2. Run individual examples with sbt:**
238
698
 
239
- **Basic lifecycle management with temporary files**
699
+ ### Basic Lifecycle Management with Temporary Files
700
+
701
+ This example demonstrates creating and automatically cleaning up temporary files using Resource's lifecycle management. It shows how Resource ensures files are closed even if exceptions occur:
240
702
 
241
703
  ```scala title="scope-examples/src/main/scala/scope/examples/TempFileHandlingExample.scala"
242
704
  /*
@@ -341,11 +803,15 @@ private def createTempFile(s: Scope, path: String, content: String): TempFile =
341
803
 
342
804
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/TempFileHandlingExample.scala))
343
805
 
806
+ Run this example with:
807
+
344
808
  ```bash
345
- sbt "scope-examples/runMain scope.examples.TempFileHandlingExample"
809
+ sbt "scope-examples/runMain scope.examples.tempFileHandlingExample"
346
810
  ```
347
811
 
348
- **Acquiring and releasing database connections**
812
+ ### Acquiring and Releasing Database Connections
813
+
814
+ This example demonstrates the acquire-release pattern using Resource to manage database connections. It shows proper connection initialization and guaranteed cleanup:
349
815
 
350
816
  ```scala title="scope-examples/src/main/scala/scope/examples/DatabaseConnectionExample.scala"
351
817
  /*
@@ -490,11 +956,15 @@ final class Database(config: DbConfig) extends AutoCloseable {
490
956
 
491
957
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/DatabaseConnectionExample.scala))
492
958
 
959
+ Run this example with:
960
+
493
961
  ```bash
494
- sbt "scope-examples/runMain scope.examples.DatabaseConnectionExample"
962
+ sbt "scope-examples/runMain scope.examples.runDatabaseExample"
495
963
  ```
496
964
 
497
- **Shared resources with memoization and reference counting**
965
+ ### Shared Resources with Memoization and Reference Counting
966
+
967
+ This example demonstrates Resource.shared to create a singleton logger instance that is automatically cleaned up only when the last service releases it. Shows reference counting in action:
498
968
 
499
969
  ```scala title="scope-examples/src/main/scala/scope/examples/CachingSharedLoggerExample.scala"
500
970
  /*
@@ -653,11 +1123,15 @@ object CachingSharedLoggerExample {
653
1123
 
654
1124
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/CachingSharedLoggerExample.scala))
655
1125
 
1126
+ Run this example with:
1127
+
656
1128
  ```bash
657
- sbt "scope-examples/runMain scope.examples.CachingSharedLoggerExample"
1129
+ sbt "scope-examples/runMain scope.examples.runCachingExample"
658
1130
  ```
659
1131
 
660
- **Managing shared expensive resources**
1132
+ ### Managing Shared Expensive Resources
1133
+
1134
+ This example demonstrates using Resource.shared for a database connection pool—an expensive resource that should exist exactly once. Shows how multiple services safely share the same pool instance with automatic cleanup:
661
1135
 
662
1136
  ```scala title="scope-examples/src/main/scala/scope/examples/ConnectionPoolExample.scala"
663
1137
  /*
@@ -822,11 +1296,15 @@ final class ConnectionPool(config: PoolConfig) extends AutoCloseable {
822
1296
 
823
1297
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/ConnectionPoolExample.scala))
824
1298
 
1299
+ Run this example with:
1300
+
825
1301
  ```bash
826
- sbt "scope-examples/runMain scope.examples.ConnectionPoolExample"
1302
+ sbt "scope-examples/runMain scope.examples.connectionPoolExample"
827
1303
  ```
828
1304
 
829
- **Transactional resource management**
1305
+ ### Transactional Resource Management
1306
+
1307
+ This example demonstrates combining Resource with transaction boundaries. Shows how to manage resources (connections, transactions) that must be coordinated across scopes with proper rollback on failure:
830
1308
 
831
1309
  ```scala title="scope-examples/src/main/scala/scope/examples/TransactionBoundaryExample.scala"
832
1310
  /*
@@ -986,11 +1464,15 @@ object TransactionBoundaryExample {
986
1464
 
987
1465
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/TransactionBoundaryExample.scala))
988
1466
 
1467
+ Run this example with:
1468
+
989
1469
  ```bash
990
- sbt "scope-examples/runMain scope.examples.TransactionBoundaryExample"
1470
+ sbt "scope-examples/runMain scope.examples.runTransactionBoundaryExample"
991
1471
  ```
992
1472
 
993
- **Multi-layer service construction**
1473
+ ### Multi-Layer Service Construction
1474
+
1475
+ This example demonstrates Resource.from macro to automatically build a complex dependency graph with multiple services. Shows automatic wiring of constructor dependencies and cleanup in correct LIFO order:
994
1476
 
995
1477
  ```scala title="scope-examples/src/main/scala/scope/examples/LayeredWebServiceExample.scala"
996
1478
  /*
@@ -1121,5 +1603,5 @@ class UserController(repo: UserRepository) extends AutoCloseable {
1121
1603
  ([source](https://github.com/zio/zio-blocks/blob/main/scope-examples/src/main/scala/scope/examples/LayeredWebServiceExample.scala))
1122
1604
 
1123
1605
  ```bash
1124
- sbt "scope-examples/runMain scope.examples.LayeredWebServiceExample"
1606
+ sbt "scope-examples/runMain scope.examples.layeredWebServiceExample"
1125
1607
  ```