@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
@@ -0,0 +1,246 @@
1
+ ---
2
+ id: defer-handle
3
+ title: "DeferHandle"
4
+ ---
5
+
6
+ `DeferHandle` is a handle returned by `Scope.defer` that allows cancelling a registered finalizer before the scope closes:
7
+
8
+ ```scala
9
+ abstract class DeferHandle {
10
+ def cancel(): Unit
11
+ }
12
+ ```
13
+
14
+ When `Scope#defer(cleanup)` is called, the cleanup action is registered and a `DeferHandle` is returned. This handle can be used to remove that finalizer early, preventing it from running when the scope closes. This is useful when a resource is explicitly released before the scope ends, and running the finalizer again would be unnecessary or harmful.
15
+
16
+ ## Construction
17
+
18
+ `DeferHandle` is not instantiated directly. Instead, it is created by calling `Scope#defer` with a cleanup action:
19
+
20
+ ```scala
21
+ import zio.blocks.scope.{Scope, DeferHandle}
22
+
23
+ trait Scope {
24
+ def defer(cleanup: => Unit): DeferHandle
25
+ }
26
+ ```
27
+
28
+ The following example demonstrates creating a `DeferHandle`:
29
+
30
+ ```scala
31
+ import zio.blocks.scope.Scope
32
+
33
+ Scope.global.scoped { scope =>
34
+ import scope._
35
+
36
+ val handle = defer {
37
+ println("This cleanup will run when scope closes, unless cancelled")
38
+ }
39
+
40
+ // The handle can now be used to cancel the cleanup
41
+ handle.cancel()
42
+ }
43
+ ```
44
+
45
+ ## Core Operations
46
+
47
+ The `DeferHandle#cancel` method removes the registered finalizer so it will not run when the scope closes:
48
+
49
+ ```scala
50
+ trait DeferHandle {
51
+ def cancel(): Unit
52
+ }
53
+ ```
54
+
55
+ This method is:
56
+
57
+ - **Thread-safe**: Can be called from any thread without synchronization
58
+ - **Idempotent**: Calling it multiple times has the same effect as calling once
59
+
60
+ If the scope has already closed (and the finalizer has already run or been discarded), calling `DeferHandle#cancel` is a no-op. In the following example, we register a cleanup action, then cancel it before the scope closes:
61
+
62
+ ```scala
63
+ import zio.blocks.scope.Scope
64
+ import java.io.ByteArrayOutputStream
65
+
66
+ Scope.global.scoped { scope =>
67
+ import scope._
68
+
69
+ val buffer = allocate(ByteArrayOutputStream())
70
+ val closeHandle = defer {
71
+ println("Auto-closing buffer")
72
+ $(buffer)(_.close())
73
+ }
74
+
75
+ // Manually close the buffer
76
+ $(buffer)(_.close())
77
+
78
+ // Cancel the automatic finalizer since we already closed it
79
+ closeHandle.cancel()
80
+ }
81
+ ```
82
+
83
+ ## Use Cases
84
+
85
+ `DeferHandle` is useful in several common scenarios:
86
+
87
+ ### Preventing Duplicate Cleanup
88
+
89
+ When a resource is explicitly released before the scope ends, cancel the automatic finalizer to avoid duplicate cleanup:
90
+
91
+ ```scala
92
+ import zio.blocks.scope.Scope
93
+ import java.io.ByteArrayOutputStream
94
+
95
+ val result = Scope.global.scoped { scope =>
96
+ import scope._
97
+
98
+ val buffer = allocate(ByteArrayOutputStream())
99
+
100
+ val finalizeHandle = defer {
101
+ println(s"Finalizer running, buffer closing")
102
+ $(buffer)(_.close())
103
+ }
104
+
105
+ // Explicit cleanup
106
+ $(buffer) { buf =>
107
+ buf.write("data".getBytes)
108
+ println(s"Manual use: buffer has ${buf.size()} bytes")
109
+ buf.close()
110
+ }
111
+
112
+ // Cancel the automatic finalizer
113
+ finalizeHandle.cancel()
114
+
115
+ "done"
116
+ }
117
+ ```
118
+
119
+ ### Conditional Cleanup
120
+
121
+ Cancel finalizers based on runtime conditions:
122
+
123
+ ```scala
124
+ import zio.blocks.scope.Scope
125
+
126
+ def acquireResource(shouldCleanup: Boolean) = Scope.global.scoped { scope =>
127
+ import scope._
128
+
129
+ val resource = "important resource"
130
+ val handle = scope.defer {
131
+ println("Cleaning up resource")
132
+ }
133
+
134
+ if (!shouldCleanup) {
135
+ handle.cancel()
136
+ println("Cleanup disabled")
137
+ }
138
+
139
+ resource
140
+ }
141
+
142
+ acquireResource(shouldCleanup = false)
143
+ acquireResource(shouldCleanup = true)
144
+ ```
145
+
146
+ ### Transferring Ownership
147
+
148
+ When transferring a resource to external management, cancel its finalizer so the external system can control cleanup:
149
+
150
+ ```scala
151
+ import zio.blocks.scope.Scope
152
+ import java.io.ByteArrayInputStream
153
+
154
+ val result = Scope.global.scoped { scope =>
155
+ import scope._
156
+
157
+ val stream = allocate(ByteArrayInputStream("data".getBytes))
158
+ val handle = defer {
159
+ println("Scope finalizer would close stream")
160
+ $(stream)(_.close())
161
+ }
162
+
163
+ // Transfer ownership to external manager
164
+ // (In real code, this might pass to a thread pool or async framework)
165
+ handle.cancel() // Let the manager handle cleanup
166
+
167
+ "transferred"
168
+ }
169
+ ```
170
+
171
+ ## Noop Handle
172
+
173
+ When `defer()` is called on an already-closed scope, a no-op handle is returned:
174
+
175
+ ```scala
176
+ import zio.blocks.scope.Scope
177
+
178
+ Scope.global.scoped { scope =>
179
+ import scope._
180
+
181
+ val handle = scope.defer {
182
+ println("This will run when scope closes")
183
+ }
184
+
185
+ // Subsequent calls to cancel() remove the finalizer
186
+ handle.cancel()
187
+
188
+ println("Finalizer has been cancelled")
189
+ }
190
+ ```
191
+
192
+ ## Thread Safety
193
+
194
+ `DeferHandle` is thread-safe. Multiple threads can call `cancel()` on the same handle without external synchronization:
195
+
196
+ ```scala
197
+ import zio.blocks.scope.Scope
198
+ import java.util.concurrent.CountDownLatch
199
+ import java.util.concurrent.Executors
200
+
201
+ val result = Scope.global.scoped { scope =>
202
+ import scope._
203
+
204
+ val handle = defer {
205
+ println("Finalizer")
206
+ }
207
+
208
+ // Use a thread pool to simulate concurrent access
209
+ val executor = Executors.newFixedThreadPool(5)
210
+ val latch = new CountDownLatch(5)
211
+
212
+ (1 to 5).foreach { i =>
213
+ executor.submit(new Runnable {
214
+ def run(): Unit = {
215
+ handle.cancel()
216
+ println(s"Thread $i cancelled")
217
+ latch.countDown()
218
+ }
219
+ })
220
+ }
221
+
222
+ // Wait for all threads to finish
223
+ latch.await()
224
+ executor.shutdown()
225
+
226
+ "completed"
227
+ }
228
+ ```
229
+
230
+ ## See Also
231
+
232
+ - [`Scope#defer`](./scope.md#registering-finalizers) — the method that returns a `DeferHandle`
233
+ - [`Finalizer`](./finalizer.md) — the trait defining `Finalizer#defer`
234
+ - [`Finalization`](./finalization.md) — the result of running all finalizers
235
+
236
+ ## Integration
237
+
238
+ `DeferHandle` is part of ZIO Blocks' resource management system. It works directly with:
239
+
240
+ - **[`Scope`](./scope.md)** — The primary way to create a `DeferHandle` is via `Scope#defer`. A scope manages multiple finalizers and runs them all when the scope closes. `DeferHandle` allows selective cancellation of individual finalizers before that happens.
241
+
242
+ - **[`Finalizer`](./finalizer.md)** — `Finalizer` defines the `Finalizer#defer` operation that returns a `DeferHandle`. It abstracts the concept of registering cleanup actions.
243
+
244
+ - **[`Finalization`](./finalization.md)** — When a scope closes, it runs all registered finalizers. A cancelled `DeferHandle` removes its associated finalizer from this process.
245
+
246
+ Together, these types form the foundation of compile-time resource safety in ZIO Blocks, allowing you to manage resource lifecycles with certainty that cleanup will occur exactly when needed.
@@ -0,0 +1,286 @@
1
+ ---
2
+ id: finalization
3
+ title: "Finalization"
4
+ ---
5
+
6
+ `Finalization` is the result of running all finalizers in a scope, collecting any errors that occurred during cleanup:
7
+
8
+ ```scala
9
+ import zio.blocks.chunk.Chunk
10
+
11
+ abstract class Finalization(val errors: Chunk[Throwable]) {
12
+ def isEmpty: Boolean
13
+ def nonEmpty: Boolean
14
+ def orThrow(): Unit
15
+ def suppress(initial: Throwable): Throwable
16
+ }
17
+ ```
18
+
19
+ When a scope closes, each registered finalizer runs in LIFO order. If any finalizer throws an exception, that error is caught and collected into a `Finalization`. This type ensures that all finalizers run even if some fail, and allows the caller to decide how to handle accumulated errors.
20
+
21
+ `Finalization` collects errors from finalizers in a `Chunk[Throwable]`. The first error in the chunk corresponds to the head of the chunk (the first finalizer that failed in LIFO execution order).
22
+
23
+ ## Core Methods
24
+
25
+ The following four methods allow you to inspect and handle errors from finalization:
26
+
27
+ ### `Finalization#isEmpty`
28
+
29
+ Returns `true` if no finalizer errors were collected:
30
+
31
+ ```scala
32
+ import zio.blocks.chunk.Chunk
33
+
34
+ abstract class Finalization(val errors: Chunk[Throwable]) {
35
+ def isEmpty: Boolean
36
+ }
37
+ ```
38
+
39
+ Here's an example of checking if finalization succeeded:
40
+
41
+ ```scala
42
+ import zio.blocks.scope.Scope
43
+
44
+ Scope.global.scoped { scope =>
45
+ import scope._
46
+
47
+ $(open()) { openScope =>
48
+ import openScope.scope._
49
+ defer {
50
+ println("Cleanup")
51
+ }
52
+ val fin = openScope.close()
53
+ if (fin.isEmpty) println("No errors") else println("Errors occurred")
54
+ }
55
+ }
56
+ ```
57
+
58
+ ### `Finalization#nonEmpty`
59
+
60
+ Returns `true` if at least one finalizer error was collected:
61
+
62
+ ```scala
63
+ import zio.blocks.chunk.Chunk
64
+
65
+ abstract class Finalization(val errors: Chunk[Throwable]) {
66
+ def nonEmpty: Boolean
67
+ }
68
+ ```
69
+
70
+ Here's an example of checking for errors:
71
+
72
+ ```scala
73
+ import zio.blocks.scope.Scope
74
+
75
+ Scope.global.scoped { scope =>
76
+ import scope._
77
+
78
+ $(open()) { openScope =>
79
+ import openScope.scope._
80
+ defer {
81
+ throw new Exception("Cleanup failed")
82
+ }
83
+ val fin = openScope.close()
84
+ if (fin.nonEmpty) {
85
+ println(s"Errors occurred: ${fin.errors.length}")
86
+ }
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### `Finalization#orThrow()`
92
+
93
+ Throws the first collected error with all remaining errors added as suppressed exceptions. Does nothing if there are no errors:
94
+
95
+ ```scala
96
+ import zio.blocks.chunk.Chunk
97
+
98
+ abstract class Finalization(val errors: Chunk[Throwable]) {
99
+ def orThrow(): Unit
100
+ }
101
+ ```
102
+
103
+ The first error corresponds to the head of the chunk (the first finalizer that failed in LIFO execution order). Remaining errors are attached as suppressed exceptions using `addSuppressed()`. Here's an example:
104
+
105
+ ```scala
106
+ import zio.blocks.scope.Scope
107
+
108
+ Scope.global.scoped { scope =>
109
+ import scope._
110
+ $(open()) { openScope =>
111
+ defer { throw Exception("Error 2") }
112
+ defer { throw Exception("Error 1") }
113
+ val fin = openScope.close()
114
+
115
+ try {
116
+ fin.orThrow()
117
+ } catch {
118
+ case e: Exception =>
119
+ println(s"Primary: ${e.getMessage}")
120
+ e.getSuppressed.foreach(s => println(s"Suppressed: ${s.getMessage}"))
121
+ }
122
+ }
123
+ }
124
+ ```
125
+
126
+ ### `Finalization#suppress(initial)`
127
+
128
+ Adds all collected finalizer errors as suppressed exceptions to `initial` and returns it. If there are no errors, `initial` is returned unchanged:
129
+
130
+ ```scala
131
+ import zio.blocks.chunk.Chunk
132
+
133
+ abstract class Finalization(val errors: Chunk[Throwable]) {
134
+ def suppress(initial: Throwable): Throwable
135
+ }
136
+ ```
137
+
138
+ This is useful when you want to preserve the original error context while attaching cleanup errors. Here's an example:
139
+
140
+ ```scala
141
+ import zio.blocks.scope.Scope
142
+
143
+ Scope.global.scoped { scope =>
144
+ import scope._
145
+
146
+ val initialError = Exception("Original error")
147
+
148
+ $(open()) { openScope =>
149
+ defer { throw Exception("Cleanup error") }
150
+ val fin = openScope.close()
151
+
152
+ val combined = fin.suppress(initialError)
153
+ println(s"Primary: ${combined.getMessage}")
154
+ combined.getSuppressed.foreach(s => println(s"Suppressed: ${s.getMessage}"))
155
+ }
156
+ }
157
+ ```
158
+
159
+ ## Error Ordering
160
+
161
+ Errors in the finalization are ordered by when finalizers ran (in LIFO sequence):
162
+
163
+ ```scala
164
+ import zio.blocks.scope.Scope
165
+
166
+ Scope.global.scoped { scope =>
167
+ import scope._
168
+
169
+ $(open()) { openScope =>
170
+ // Registered first, runs last (LIFO)
171
+ defer { throw Exception("Error 1") }
172
+
173
+ // Registered second, runs first
174
+ defer { throw Exception("Error 2") }
175
+
176
+ // Registered third, runs first
177
+ defer { throw Exception("Error 3") }
178
+
179
+ val fin = openScope.close()
180
+
181
+ // Errors list order: [Error 3, Error 2, Error 1]
182
+ println(s"First error: ${fin.errors.head.getMessage}")
183
+ println(s"Total errors: ${fin.errors.length}")
184
+ }
185
+ }
186
+ ```
187
+
188
+ ## Use Cases
189
+
190
+ Here are common scenarios where finalization handling is useful:
191
+
192
+ ### Conditional Error Handling
193
+
194
+ Check if errors occurred and handle them appropriately:
195
+
196
+ ```scala
197
+ import zio.blocks.scope.Scope
198
+
199
+ Scope.global.scoped { scope =>
200
+ import scope._
201
+
202
+ $(open()) { openScope =>
203
+ defer {
204
+ println("Cleanup complete")
205
+ }
206
+ val fin = openScope.close()
207
+
208
+ if (fin.nonEmpty) {
209
+ fin.orThrow()
210
+ } else {
211
+ println("No errors during finalization")
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Combining Multiple Error Sources
218
+
219
+ Attach cleanup errors to an existing error:
220
+
221
+ ```scala
222
+ import zio.blocks.scope.Scope
223
+
224
+ def doWork(): Unit = {
225
+ Scope.global.scoped { scope =>
226
+ import scope._
227
+
228
+ try {
229
+ throw Exception("Work failed")
230
+ } catch {
231
+ case workError: Exception =>
232
+ $(open()) { openScope =>
233
+ defer { throw Exception("Cleanup failed") }
234
+ val fin = openScope.close()
235
+ val combined = fin.suppress(workError)
236
+ throw combined
237
+ }
238
+ }
239
+ () // Return unit on normal path
240
+ }
241
+ }
242
+
243
+ try {
244
+ doWork()
245
+ } catch {
246
+ case e: Exception =>
247
+ println(s"Work: ${e.getMessage}")
248
+ e.getSuppressed.foreach(s => println(s"During cleanup: ${s.getMessage}"))
249
+ }
250
+ ```
251
+
252
+ ### Logging All Cleanup Errors
253
+
254
+ Inspect and log all errors without stopping execution:
255
+
256
+ ```scala
257
+ import zio.blocks.scope.Scope
258
+
259
+ Scope.global.scoped { scope =>
260
+ import scope._
261
+
262
+ $(open()) { openScope =>
263
+ defer { throw Exception("Error 1") }
264
+ defer { throw Exception("Error 2") }
265
+ val fin = openScope.close()
266
+
267
+ if (fin.nonEmpty) {
268
+ println(s"Finalization collected ${fin.errors.length} errors:")
269
+ fin.errors.foreach(e => println(s" - ${e.getMessage}"))
270
+ }
271
+ }
272
+ }
273
+ ```
274
+
275
+ ## Relationship to Scope
276
+
277
+ `Finalization` is returned by:
278
+
279
+ - `Scope.open().close()` — when explicitly closing a scope
280
+ - `Scope.global.runFinalizers()` — when running global finalizers on shutdown
281
+
282
+ ## See Also
283
+
284
+ - [`Scope#defer`](./scope.md) — registers finalizers that produce errors
285
+ - [`DeferHandle`](./defer-handle.md) — handle for cancelling finalizers
286
+ - [`Finalizer`](./finalizer.md) — the trait for registering cleanup actions
@@ -0,0 +1,167 @@
1
+ ---
2
+ id: finalizer
3
+ title: "Finalizer"
4
+ ---
5
+
6
+ `Finalizer` is a minimal capability interface for registering cleanup actions. It exposes only the `Finalizer#defer` method, preventing code from accessing scope internals like resource allocation or closing.
7
+
8
+ The structural definition:
9
+
10
+ ```scala
11
+ trait Finalizer {
12
+ def defer(f: => Unit): DeferHandle
13
+ }
14
+ ```
15
+
16
+ This trait serves as a boundary between scope management internals and user code that only needs to register cleanup actions. By exposing only `defer`, code can safely request cleanup registration without requiring full scope access.
17
+
18
+ ## Motivation / Use Case
19
+
20
+ `Finalizer` integrates with `Scope` to enable resource management patterns:
21
+
22
+ ```scala
23
+ import zio.blocks.scope.{Scope, Finalizer}
24
+
25
+ def openConnection(url: String)(implicit fin: Finalizer): String = {
26
+ fin.defer {
27
+ println(s"Closing connection to $url")
28
+ }
29
+ s"Connected to $url"
30
+ }
31
+
32
+ Scope.global.scoped { scope =>
33
+ import scope._
34
+ openConnection("https://example.com")
35
+ // Connection closes when scope exits
36
+ ()
37
+ }
38
+ ```
39
+
40
+ By decoupling code that needs cleanup registration from code that manages the complete scope lifecycle, `Finalizer` allows functions to safely register finalizers without requiring full scope access.
41
+
42
+ ## Construction / Creating Instances
43
+
44
+ `Finalizer` is not typically constructed directly. Instead, it is obtained through a scope:
45
+
46
+ ### From a `Scope`
47
+
48
+ Any `Scope` instance can be used as a `Finalizer` since `Scope extends Finalizer`:
49
+
50
+ ```scala
51
+ import zio.blocks.scope.Scope
52
+
53
+ Scope.global.scoped { scope =>
54
+ import scope._
55
+ // scope is both a Scope and a Finalizer
56
+ val handle = scope.defer {
57
+ println("Cleanup")
58
+ }
59
+ () // Return unit
60
+ }
61
+ ```
62
+
63
+ ### As a Context Bound
64
+
65
+ Functions can request a `Finalizer` via `implicit` parameter, enabling decoupled cleanup registration:
66
+
67
+ ```scala
68
+ import zio.blocks.scope.Finalizer
69
+
70
+ def setupResource(name: String)(implicit fin: Finalizer): String = {
71
+ fin.defer {
72
+ println(s"Closing $name")
73
+ }
74
+ name
75
+ }
76
+ ```
77
+
78
+ ## Core Operations
79
+
80
+ The `Finalizer` interface provides a single core operation for registering cleanup handlers:
81
+
82
+ ### `Finalizer#defer`
83
+
84
+ Registers a finalizer (cleanup action) to run when the scope closes. The cleanup action runs in LIFO order along with other finalizers registered on the same scope. Returns a `DeferHandle` that can cancel the registration before the scope closes:
85
+
86
+ ```scala
87
+ import zio.blocks.scope.Scope
88
+
89
+ Scope.global.scoped { scope =>
90
+ import scope._
91
+
92
+ val handle1 = scope.defer {
93
+ println("Cleanup 1")
94
+ }
95
+
96
+ val handle2 = scope.defer {
97
+ println("Cleanup 2")
98
+ }
99
+
100
+ // Finalizers run in LIFO: Cleanup 2, then Cleanup 1
101
+ // Can cancel before scope closes
102
+ handle1.cancel()
103
+ // Now only Cleanup 2 runs
104
+ }
105
+ ```
106
+
107
+ ### Package-Level `defer` Helper
108
+
109
+ A package-level convenience function allows writing `defer { cleanup }` when a `Finalizer` is in scope. The signature is:
110
+
111
+ ```scala
112
+ def defer(finalizer: => Unit)(implicit fin: Finalizer): DeferHandle
113
+ ```
114
+
115
+ This removes the need to write `fin.defer { cleanup }`. Here's the convenience function in use:
116
+
117
+ ```scala
118
+ import zio.blocks.scope.{Scope, defer, Finalizer}
119
+
120
+ def setupWithCleanup()(implicit fin: Finalizer) = {
121
+ defer {
122
+ println("Cleanup")
123
+ }
124
+ }
125
+
126
+ Scope.global.scoped { scope =>
127
+ import scope._
128
+ setupWithCleanup()
129
+ // Cleanup prints when scope closes
130
+ () // Return unit (which is Unscoped)
131
+ }
132
+ ```
133
+
134
+ ## Integration
135
+
136
+ `Finalizer` is a supertrait of `Scope`. The structural definition shows this relationship:
137
+
138
+ ```scala
139
+ sealed abstract class Scope extends Finalizer with ScopeVersionSpecific
140
+ ```
141
+
142
+ This means any `Scope` instance can be used where a `Finalizer` is expected. However, the converse is not true—a `Finalizer` reference does not provide `Scope#allocate`, `Scope#$`, or other scope operations.
143
+
144
+ ## Finalization Order
145
+
146
+ Finalizers registered with `Finalizer#defer` run in **LIFO order** (last registered runs first) when the scope closes. This ensures that resources acquired in order can be cleaned up in reverse order:
147
+
148
+ ```scala
149
+ import zio.blocks.scope.Scope
150
+
151
+ Scope.global.scoped { scope =>
152
+ import scope._
153
+
154
+ scope.defer { println("First registered, runs last") }
155
+ scope.defer { println("Second registered, runs first") }
156
+ // Output on scope close:
157
+ // Second registered, runs first
158
+ // First registered, runs last
159
+ () // Return unit (which is Unscoped)
160
+ }
161
+ ```
162
+
163
+ ## See Also
164
+
165
+ - [`Scope`](./scope.md) — the full scope lifecycle management
166
+ - [`DeferHandle`](./defer-handle.md) — the handle returned by `Finalizer#defer` for cancellation
167
+ - [`Finalization`](./finalization.md) — the result of running all finalizers