@zio.dev/zio-blocks 0.0.1

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/scope.md ADDED
@@ -0,0 +1,627 @@
1
+ # ZIO Blocks — Scope (compile-time safe resource management)
2
+
3
+ `zio.blocks.scope` provides **compile-time verified resource safety** for synchronous code by tagging values with an unnameable, type-level **scope identity**. Values allocated in a scope can only be used when you hold a compatible `Scope`, and values allocated in a *child* scope cannot be returned to the parent in a usable form.
4
+
5
+ If you've used `try/finally`, `Using`, or ZIO `Scope`, this library lives in the same problem space, but it focuses on:
6
+
7
+ - **Compile-time prevention of scope leaks**
8
+ - **Zero runtime overhead for the scoped tag** (`A @@ S` is represented as `A`)
9
+ - **Simple, synchronous lifecycle management** (finalizers run LIFO on scope close)
10
+
11
+ ---
12
+
13
+ ## Table of contents
14
+
15
+ - [Quick start](#quick-start)
16
+ - [Core concepts](#core-concepts)
17
+ - [1) `Scope[ParentTag, Tag]`](#1-scopeparenttag-tag)
18
+ - [2) Scoped values: `A @@ S`](#2-scoped-values-a--s)
19
+ - [3) `Resource[A]`: acquisition + finalization](#3-resourcea-acquisition--finalization)
20
+ - [4) `Scoped[Tag, A]`: deferred computations](#4-scopedtag-a-deferred-computations)
21
+ - [5) `ScopeEscape` and `Unscoped`: what may escape](#5-scopeescape-and-unscoped-what-may-escape)
22
+ - [6) `Wire[-In, +Out]`: dependency recipes](#6-wire-in-out-dependency-recipes)
23
+ - [7) `Wireable[Out]`: DI for traits/abstract classes](#7-wireableout-di-for-traitsabstract-classes)
24
+ - [Safety model (why leaking is prevented)](#safety-model-why-leaking-is-prevented)
25
+ - [Usage examples](#usage-examples)
26
+ - [Allocating and using a resource](#allocating-and-using-a-resource)
27
+ - [Nested scopes (child can use parent, not vice versa)](#nested-scopes-child-can-use-parent-not-vice-versa)
28
+ - [Building a `Scoped` program (map/flatMap)](#building-a-scoped-program-mapflatmap)
29
+ - [Registering cleanup manually with `defer`](#registering-cleanup-manually-with-defer)
30
+ - [Dependency injection with `Wire` + `Context`](#dependency-injection-with-wire--context)
31
+ - [Supplying dependencies with `Resource.from[T](wire1, wire2, ...)`](#supplying-dependencies-with-resourcefromtwire1-wire2-)
32
+ - [DI for traits via `Wireable`](#di-for-traits-via-wireable)
33
+ - [Interop escape hatch: `leak`](#interop-escape-hatch-leak)
34
+ - [API reference (selected)](#api-reference-selected)
35
+
36
+ ---
37
+
38
+ ## Quick start
39
+
40
+ ```scala
41
+ import zio.blocks.scope._
42
+
43
+ final class Database extends AutoCloseable {
44
+ def query(sql: String): String = s"result: $sql"
45
+ def close(): Unit = println("db closed")
46
+ }
47
+
48
+ Scope.global.scoped { scope =>
49
+ val db: Database @@ scope.Tag =
50
+ scope.allocate(Resource(new Database))
51
+
52
+ val result: String =
53
+ scope.$(db)(_.query("SELECT 1"))
54
+
55
+ println(result)
56
+ }
57
+ ```
58
+
59
+ Key things to notice:
60
+
61
+ - `scope.allocate(...)` returns a **scoped** value: `Database @@ scope.Tag`
62
+ - You **cannot** call `db.query(...)` directly (methods are intentionally hidden)
63
+ - You must use `scope.$(db)(...)` or build a `Scoped` computation
64
+ - When the `scoped { ... }` block exits, finalizers run **LIFO** and errors are handled safely
65
+
66
+ ---
67
+
68
+ ## Core concepts
69
+
70
+ ### 1) `Scope[ParentTag, Tag]`
71
+
72
+ A `Scope` manages finalizers and ties values to a *type-level identity* called a **Tag**.
73
+
74
+ - `Scope[ParentTag, Tag]` has **two** type parameters:
75
+ - `ParentTag`: the parent scope's tag (capability boundary)
76
+ - `Tag <: ParentTag`: this scope's unique identity (used to tag values)
77
+
78
+ Every `Scope` also exposes a *path-dependent* member type:
79
+
80
+ ```scala
81
+ type Tag = Tag0
82
+ ```
83
+
84
+ So in code you'll typically write:
85
+
86
+ ```scala
87
+ Scope.global.scoped { scope =>
88
+ val x: Something @@ scope.Tag = ???
89
+ }
90
+ ```
91
+
92
+ #### Global scope
93
+
94
+ `Scope.global` is the root of the tag hierarchy:
95
+
96
+ ```scala
97
+ object Scope {
98
+ type GlobalTag
99
+ lazy val global: Scope[GlobalTag, GlobalTag]
100
+ }
101
+ ```
102
+
103
+ - The global scope is intended to live for the lifetime of the process.
104
+ - Its finalizers run on JVM shutdown.
105
+ - Values allocated in `Scope.global` typically **escape** as raw values via `ScopeEscape` (see below).
106
+
107
+ ---
108
+
109
+ ### 2) Scoped values: `A @@ S`
110
+
111
+ `A @@ S` means: "a value of type `A` that is locked to scope tag `S`".
112
+
113
+ - **Runtime representation:** just `A` (no wrapper allocation)
114
+ - **Key effect:** methods on `A` are hidden, so you can't call `a.method` without proving scope access
115
+ - **Access paths:**
116
+ - `scope.$(a)(f)` to use a scoped value immediately
117
+ - `a.map / a.flatMap` to build a `Scoped` computation, then run it via `scope(scoped)`
118
+
119
+ #### Scala 3 vs Scala 2 note
120
+
121
+ - In **Scala 3**, `@@` is implemented as an `opaque` type.
122
+ - In **Scala 2**, the library emulates the same "opaque-like" behavior using the *module pattern* (still zero-overhead at runtime).
123
+
124
+ ---
125
+
126
+ ### 3) `Resource[A]`: acquisition + finalization
127
+
128
+ `Resource[A]` describes how to **acquire** an `A` and how to **release** it when a scope closes. It is intentionally lazy: you *describe what to do*, and allocation happens only through:
129
+
130
+ ```scala
131
+ scope.allocate(resource)
132
+ ```
133
+
134
+ Common constructors:
135
+
136
+ - `Resource(a)`
137
+ - Wraps a by-name value; if it's `AutoCloseable`, `close()` is registered automatically.
138
+ - `Resource.acquireRelease(acquire)(release)`
139
+ - Explicit lifecycle.
140
+ - `Resource.fromAutoCloseable(thunk)`
141
+ - A type-safe helper for `AutoCloseable`.
142
+ - `Resource.from[T]` (macro)
143
+ - Derives a resource from `T`'s constructor.
144
+ - If `T` is `AutoCloseable`, registers `close()` automatically.
145
+ - If `T` has dependencies, either:
146
+ - use `Wire` + `toResource(deps)`, or
147
+ - use `Resource.from[T](wire1, wire2, ...)` (see below).
148
+
149
+ #### Resource "sharing" vs "uniqueness"
150
+
151
+ `Resource` has two important internal flavors:
152
+
153
+ - `Resource.Unique[A]`
154
+ - Produces a **fresh** instance every time you allocate it (typical for `Resource(...)`, `acquireRelease`, etc.).
155
+ - `Resource.Shared[A]`
156
+ - Produces a **shared** instance per `Resource.Shared` value, with **reference counting**:
157
+ - the first allocation initializes the value and collects finalizers
158
+ - each allocating scope registers a decrement finalizer
159
+ - when the reference count reaches zero, the collected finalizers run
160
+
161
+ **Important clarification:** sharing is **not** "memoized within a Wire graph" or "within a scope" by magic. Sharing happens **within the specific `Resource.Shared` instance** you reuse.
162
+
163
+ ---
164
+
165
+ ### 4) `Scoped[Tag, A]`: deferred computations
166
+
167
+ `Scoped[-Tag, +A]` represents a computation that produces `A`, but can only be executed by a scope whose tag is compatible with `Tag`.
168
+
169
+ Execution happens via:
170
+
171
+ ```scala
172
+ scope(scopedComputation)
173
+ ```
174
+
175
+ How to build them:
176
+
177
+ - From scoped values:
178
+ - `val s: Scoped[S, B] = (a: A @@ S).map(f)`
179
+ - `flatMap` composes scoped values while tracking combined requirements
180
+ - Or directly:
181
+ - `Scoped.create(() => ...)` (advanced/internal style)
182
+
183
+ `Scoped` is contravariant in `Tag`, which is what allows a **child** scope (more specific tag) to run computations that only require a **parent** tag.
184
+
185
+ ---
186
+
187
+ ### 5) `ScopeEscape` and `Unscoped`: what may escape
188
+
189
+ Whenever you access a scoped value via:
190
+
191
+ - `scope.$(value)(f)`, or
192
+ - `scope(scopedComputation)`,
193
+
194
+ …the return type is controlled by `ScopeEscape[A, S]`, which decides whether a result:
195
+
196
+ - escapes as raw `A`, or
197
+ - remains tracked as `A @@ S`.
198
+
199
+ Rule of thumb:
200
+
201
+ - Pure data (e.g. `Int`, `String`, small case classes you mark `Unscoped`) should escape as raw values.
202
+ - Resource-like values should remain scoped unless you explicitly `leak`.
203
+
204
+ ---
205
+
206
+ ### 6) `Wire[-In, +Out]`: dependency recipes
207
+
208
+ `Wire` is a recipe for constructing services, commonly used for dependency injection.
209
+
210
+ - `In` is the required dependencies (provided as a `Context[In]`)
211
+ - `Out` is the produced service
212
+
213
+ There are two wire flavors:
214
+
215
+ - `Wire.Shared`: a shared recipe
216
+ - `Wire.Unique`: a unique recipe
217
+
218
+ **Important clarification:** `Wire` itself is just a recipe. The actual memoization/sharing behavior happens when you convert the wire into a `Resource`:
219
+
220
+ ```scala
221
+ val r: Resource[Out] = wire.toResource(deps)
222
+ val out: Out @@ scope.Tag = scope.allocate(r)
223
+ ```
224
+
225
+ - `Wire.Shared#toResource` produces a `Resource.Shared`, which is where the reference-counted sharing is implemented.
226
+ - `Wire.Unique#toResource` produces a `Resource.Unique`.
227
+
228
+ Macros available at package level:
229
+
230
+ - `shared[T]`: derive a shared wire from `T`'s constructor (or from a `Wireable[T]` if present)
231
+ - `unique[T]`: derive a unique wire
232
+
233
+ ---
234
+
235
+ ### 7) `Wireable[Out]`: DI for traits/abstract classes
236
+
237
+ `shared[T]` / `unique[T]` can derive wires from **concrete classes** with constructors. But traits and abstract classes are not instantiable, so you need a way to tell the macros "when someone asks for `T`, build it like *this*".
238
+
239
+ That's what `Wireable[T]` is: a typeclass that supplies a `Wire` for a service.
240
+
241
+ Typical use:
242
+
243
+ - Define a `Wireable[MyTrait]` in `MyTrait`'s companion object.
244
+ - `shared[MyTrait]` or `unique[MyTrait]` will pick it up automatically.
245
+
246
+ This is especially useful when you want to inject an interface but construct a concrete implementation (and still register finalizers correctly).
247
+
248
+ ---
249
+
250
+ ## Safety model (why leaking is prevented)
251
+
252
+ The library prevents scope leaks via two reinforcing mechanisms:
253
+
254
+ ### A) Existential child tags (fresh, unnameable types)
255
+
256
+ Child scopes are created with:
257
+
258
+ ```scala
259
+ Scope.global.scoped { scope =>
260
+ scope.scoped { child =>
261
+ // allocate in child
262
+ }
263
+ }
264
+ ```
265
+
266
+ The child scope has an existential tag (fresh per invocation). You can allocate in the child, but you can't return those values to the parent in a usable form because the parent cannot name (or satisfy) the child tag.
267
+
268
+ Compile-time safety is verified in tests, e.g.:
269
+ `ScopeCompileTimeSafetyScala3Spec`.
270
+
271
+ ### B) Tag invariance + "opaque-like" `@@` blocks subtyping escape
272
+
273
+ Even if you try to "widen" a child-tagged value to a parent-tagged value, invariance and hidden members prevent it from typechecking. The only sanctioned access route is through `scope.$` / `scope.apply`, which require tag evidence.
274
+
275
+ ---
276
+
277
+ ## Usage examples
278
+
279
+ ### Allocating and using a resource
280
+
281
+ ```scala
282
+ import zio.blocks.scope._
283
+
284
+ final class FileHandle(path: String) extends AutoCloseable {
285
+ def readAll(): String = s"contents of $path"
286
+ def close(): Unit = println(s"closed $path")
287
+ }
288
+
289
+ Scope.global.scoped { scope =>
290
+ val h = scope.allocate(Resource(new FileHandle("data.txt")))
291
+
292
+ val contents: String =
293
+ scope.$(h)(_.readAll())
294
+
295
+ println(contents)
296
+ }
297
+ ```
298
+
299
+ ---
300
+
301
+ ### Nested scopes (child can use parent, not vice versa)
302
+
303
+ ```scala
304
+ import zio.blocks.scope._
305
+
306
+ Scope.global.scoped { parent =>
307
+ val parentDb = parent.allocate(Resource(new Database))
308
+
309
+ parent.scoped { child =>
310
+ // child can use parent-scoped values:
311
+ val ok: String = child.$(parentDb)(_.query("SELECT 1"))
312
+ println(ok)
313
+
314
+ val childDb = child.allocate(Resource(new Database))
315
+
316
+ // You can use childDb *inside* the child:
317
+ val ok2: String = child.$(childDb)(_.query("SELECT 2"))
318
+ println(ok2)
319
+
320
+ // But you cannot return childDb to the parent in a usable way:
321
+ // childDb : Database @@ child.Tag
322
+ // parent cannot prove parent.Tag <:< child.Tag
323
+ }
324
+
325
+ // parentDb is still usable here:
326
+ val stillOk = parent.$(parentDb)(_.query("SELECT 3"))
327
+ println(stillOk)
328
+ }
329
+ ```
330
+
331
+ ---
332
+
333
+ ### Building a `Scoped` program (map/flatMap)
334
+
335
+ ```scala
336
+ import zio.blocks.scope._
337
+
338
+ Scope.global.scoped { scope =>
339
+ val db = scope.allocate(Resource(new Database))
340
+
341
+ val program: Scoped[scope.Tag, String] =
342
+ for {
343
+ a <- db.map(_.query("SELECT 1"))
344
+ b <- db.map(_.query("SELECT 2"))
345
+ } yield s"$a | $b"
346
+
347
+ val result: String = scope(program)
348
+ println(result)
349
+ }
350
+ ```
351
+
352
+ ---
353
+
354
+ ### Registering cleanup manually with `defer`
355
+
356
+ Use `scope.defer` when you already have a value and just need to register cleanup.
357
+
358
+ ```scala
359
+ import zio.blocks.scope._
360
+
361
+ Scope.global.scoped { scope =>
362
+ val handle = new java.io.ByteArrayInputStream(Array[Byte](1, 2, 3))
363
+
364
+ scope.defer { handle.close() }
365
+
366
+ val firstByte = handle.read()
367
+ println(firstByte)
368
+ }
369
+ ```
370
+
371
+ There is also a package-level helper `defer` that only requires a `Finalizer`:
372
+
373
+ ```scala
374
+ import zio.blocks.scope._
375
+
376
+ Scope.global.scoped { scope =>
377
+ given Finalizer = scope
378
+ defer { println("cleanup") }
379
+ }
380
+ ```
381
+
382
+ ---
383
+
384
+ ### Classes with `Finalizer` parameters
385
+
386
+ If your class needs to register cleanup logic, accept a `Finalizer` parameter (not `Scope`). The wire and resource macros automatically inject the `Finalizer` when constructing such classes.
387
+
388
+ ```scala
389
+ import zio.blocks.scope._
390
+
391
+ class ConnectionPool(config: Config)(implicit finalizer: Finalizer) {
392
+ private val pool = createPool(config)
393
+ finalizer.defer { pool.shutdown() }
394
+
395
+ def getConnection(): Connection = pool.acquire()
396
+ }
397
+
398
+ // The macro sees the implicit Finalizer and injects it automatically:
399
+ val resource = Resource.from[ConnectionPool](Wire(Config("jdbc://localhost")))
400
+
401
+ Scope.global.scoped { scope =>
402
+ val pool = scope.allocate(resource)
403
+ // pool.shutdown() will be called when scope closes
404
+ }
405
+ ```
406
+
407
+ Why `Finalizer` instead of `Scope`?
408
+ - `Finalizer` is the minimal interface—it only has `defer`
409
+ - Classes that need cleanup should not have access to `allocate` or `$`
410
+ - The macros pass a `Finalizer` at runtime, so declaring `Scope` would be misleading
411
+
412
+ ---
413
+
414
+ ### Dependency injection with `Wire` + `Context`
415
+
416
+ ```scala
417
+ import zio.blocks.scope._
418
+ import zio.blocks.context.Context
419
+
420
+ final case class Config(debug: Boolean)
421
+
422
+ val w: Wire.Shared[Boolean, Config] = shared[Config]
423
+ val deps: Context[Boolean] = Context[Boolean](true)
424
+
425
+ Scope.global.scoped { scope =>
426
+ val cfg: Config @@ scope.Tag =
427
+ scope.allocate(w.toResource(deps))
428
+
429
+ val debug: Boolean =
430
+ scope.$(cfg)(_.debug) // Boolean typically escapes
431
+
432
+ println(debug)
433
+ }
434
+ ```
435
+
436
+ Sharing vs uniqueness at the wire level:
437
+
438
+ ```scala
439
+ import zio.blocks.scope._
440
+
441
+ val ws = shared[Config] // shared recipe; sharing happens via Resource.Shared when allocated
442
+ val wu = unique[Config] // unique recipe; each allocation is fresh
443
+ ```
444
+
445
+ ---
446
+
447
+ ### Supplying dependencies with `Resource.from[T](wire1, wire2, ...)`
448
+
449
+ `Resource.from[T]` can also be used as a "standalone mini graph" by providing wires for constructor dependencies.
450
+
451
+ ```scala
452
+ import zio.blocks.scope._
453
+ import zio.blocks.context.Context
454
+
455
+ final case class Config(url: String)
456
+
457
+ trait Logger {
458
+ def info(msg: String): Unit
459
+ }
460
+
461
+ final class ConsoleLogger extends Logger {
462
+ def info(msg: String): Unit = println(msg)
463
+ }
464
+
465
+ final class Service(cfg: Config, logger: Logger) extends AutoCloseable {
466
+ def run(): Unit = logger.info(s"running with ${cfg.url}")
467
+ def close(): Unit = println("service closed")
468
+ }
469
+
470
+ // Provide wires for *all* dependencies of Service:
471
+ val serviceResource: Resource[Service] =
472
+ Resource.from[Service](
473
+ Wire(Config("jdbc:postgresql://localhost/db")),
474
+ Wire(new ConsoleLogger: Logger)
475
+ )
476
+
477
+ Scope.global.scoped { scope =>
478
+ val svc = scope.allocate(serviceResource)
479
+ scope.$(svc)(_.run())
480
+ }
481
+ ```
482
+
483
+ Notes:
484
+ - All dependencies of `T` must be covered by the provided wires, otherwise you get a compile-time error.
485
+ - If `T` is `AutoCloseable`, `close()` is registered automatically.
486
+
487
+ ---
488
+
489
+ ### DI for traits via `Wireable`
490
+
491
+ When you want to inject a trait (or abstract class), define a `Wireable` in the companion so `shared[T]` / `unique[T]` can resolve it.
492
+
493
+ ```scala
494
+ import zio.blocks.scope._
495
+ import zio.blocks.context.Context
496
+
497
+ trait DatabaseApi {
498
+ def query(sql: String): String
499
+ }
500
+
501
+ final class LiveDatabaseApi(cfg: Config) extends DatabaseApi with AutoCloseable {
502
+ def query(sql: String): String = s"[${cfg.url}] $sql"
503
+ def close(): Unit = println("LiveDatabaseApi closed")
504
+ }
505
+
506
+ object DatabaseApi {
507
+ // Tell Scope how to build the trait by wiring a concrete implementation.
508
+ given Wireable.Typed[Config, DatabaseApi] =
509
+ Wireable.fromWire(shared[LiveDatabaseApi].shared.asInstanceOf[Wire[Config, DatabaseApi]])
510
+ }
511
+
512
+ final case class Config(url: String)
513
+
514
+ Scope.global.scoped { scope =>
515
+ val deps = Context(Config("jdbc:postgresql://localhost/db"))
516
+
517
+ val db: DatabaseApi @@ scope.Tag =
518
+ scope.allocate(shared[DatabaseApi].toResource(deps))
519
+
520
+ val out: String =
521
+ scope.$(db)(_.query("SELECT 1"))
522
+
523
+ println(out)
524
+ }
525
+ ```
526
+
527
+ Practical guidance:
528
+ - Prefer `Wireable.fromWire(...)` when you already have a `Wire` you trust.
529
+ - Put `given Wireable[...]` / `implicit val wireable: Wireable[...]` in the companion of the trait being injected.
530
+
531
+ ---
532
+
533
+ ### Interop escape hatch: `leak`
534
+
535
+ Sometimes you must hand a raw value to code that cannot work with `@@` types.
536
+
537
+ ```scala
538
+ import zio.blocks.scope._
539
+
540
+ Scope.global.scoped { scope =>
541
+ val db = scope.allocate(Resource(new Database))
542
+
543
+ val raw: Database = leak(db) // emits a compiler warning
544
+ // thirdParty(raw)
545
+ }
546
+ ```
547
+
548
+ **Warning:** leaking bypasses compile-time guarantees. The value may be used after its scope closes. Use only when unavoidable.
549
+
550
+ ---
551
+
552
+ ## API reference (selected)
553
+
554
+ ### `Scope`
555
+
556
+ Core methods (Scala 3 `using` vs Scala 2 `implicit` differs, but the shapes are the same):
557
+
558
+ ```scala
559
+ final class Scope[ParentTag, Tag0 <: ParentTag] {
560
+ type Tag = Tag0
561
+
562
+ def allocate[A](resource: Resource[A]): A @@ Tag
563
+ def defer(f: => Unit): Unit
564
+
565
+ def $[A, B, S](scoped: A @@ S)(f: A => B)(
566
+ using ev: this.Tag <:< S,
567
+ escape: ScopeEscape[B, S]
568
+ ): escape.Out
569
+
570
+ def apply[A, S](scoped: Scoped[S, A])(
571
+ using ev: this.Tag <:< S,
572
+ escape: ScopeEscape[A, S]
573
+ ): escape.Out
574
+
575
+ // Creates a child scope with an existential tag (fresh per call)
576
+ def scoped[A](f: Scope[this.Tag, ? <: this.Tag] => A): A
577
+ }
578
+ ```
579
+
580
+ ### `Resource`
581
+
582
+ ```scala
583
+ sealed trait Resource[+A]
584
+
585
+ object Resource {
586
+ def apply[A](value: => A): Resource[A]
587
+ def acquireRelease[A](acquire: => A)(release: A => Unit): Resource[A]
588
+ def fromAutoCloseable[A <: AutoCloseable](thunk: => A): Resource[A]
589
+
590
+ // Macro-derived constructors:
591
+ def from[T]: Resource[T]
592
+ def from[T](wires: Wire[?, ?]*): Resource[T]
593
+
594
+ // Internal / produced by wires:
595
+ def shared[A](f: Finalizer => A): Resource.Shared[A]
596
+ def unique[A](f: Finalizer => A): Resource.Unique[A]
597
+
598
+ final class Shared[+A] extends Resource[A]
599
+ final class Unique[+A] extends Resource[A]
600
+ }
601
+ ```
602
+
603
+ ### `Wire` and `Wireable`
604
+
605
+ ```scala
606
+ sealed trait Wire[-In, +Out] {
607
+ def isShared: Boolean
608
+ def shared: Wire.Shared[In, Out]
609
+ def unique: Wire.Unique[In, Out]
610
+ def toResource(deps: zio.blocks.context.Context[In]): Resource[Out]
611
+ }
612
+
613
+ trait Wireable[+Out] {
614
+ type In
615
+ def wire: Wire[In, Out]
616
+ }
617
+ ```
618
+
619
+ ---
620
+
621
+ ## Mental model recap
622
+
623
+ - Use `Scope.global.scoped { scope => ... }` to create a safe region.
624
+ - Allocate managed things with `scope.allocate(Resource(...))` (or `Resource.from[...]`).
625
+ - Use scoped values only via `scope.$(value)(...)` or via `Scoped` computations executed by `scope(scoped)`.
626
+ - Nest with `scope.scoped { child => ... }` to create a tighter lifetime boundary.
627
+ - If it doesn't typecheck, it would have been unsafe at runtime.
package/sidebars.js ADDED
@@ -0,0 +1,30 @@
1
+ const sidebars = {
2
+ sidebar: [
3
+ {
4
+ type: "category",
5
+ label: "ZIO Blocks",
6
+ collapsed: false,
7
+ link: { type: "doc", id: "index" },
8
+ items: [
9
+ "reference/schema",
10
+ "reference/reflect",
11
+ "reference/binding",
12
+ "reference/registers",
13
+ "reference/typeid",
14
+ "reference/dynamic-value",
15
+ "reference/optics",
16
+ "reference/chunk",
17
+ "reference/validation",
18
+ "reference/schema-evolution",
19
+ "reference/context",
20
+ "reference/docs",
21
+ "reference/formats",
22
+ "reference/json",
23
+ "reference/json-schema",
24
+ "reference/syntax",
25
+ ]
26
+ }
27
+ ]
28
+ };
29
+
30
+ module.exports = sidebars;