1/**
2 * Controls how long resources stay open.
3 *
4 * A scope is a lifetime boundary. Code can register cleanup effects on it, and
5 * closing the scope runs those cleanups with the `Exit` value that ended the
6 * work. Most application code uses higher-level APIs such as `Effect.scoped`
7 * and `Layer`, while this module is useful when code needs to create, provide,
8 * fork, close, or inspect scopes directly.
9 *
10 * @since 2.0.0
11 */
12
13import type * as Context from "./Context.ts"
14import type { Effect } from "./Effect.ts"
15import type { Exit } from "./Exit.ts"
16import * as effect from "./internal/effect.ts"
17
18const TypeId = effect.ScopeTypeId
19const CloseableTypeId = effect.ScopeCloseableTypeId
20
21/**
22 * A `Scope` represents a context where resources can be acquired and
23 * automatically cleaned up when the scope is closed. Scopes can use
24 * either sequential or parallel finalization strategies.
25 *
26 * **Example** (Managing scoped resources)
27 *
28 * ```ts import.meta.vitest
29 * import { Effect, Exit, Scope } from "effect"
30 *
31 * const program = Effect.gen(function*() {
32 * const scope = yield* Scope.make("sequential")
33 *
34 * const initial = [scope.strategy, scope.state._tag]
35 * yield* Scope.close(scope, Exit.void)
36 * return [initial, scope.state._tag]
37 * })
38 *
39 * Effect.runSync(program) // => [["sequential", "Empty"], "Closed"]
40 * ```
41 *
42 * @category services
43 * @since 2.0.0
44 */
45export interface Scope {
46 readonly [TypeId]: typeof TypeId
47 readonly strategy: "sequential" | "parallel"
48 readonly parent: Scope | undefined
49 state: State.Open | State.Closed | State.Empty
50}
51/**
52 * A `Closeable` scope extends the base `Scope` interface with the ability
53 * to be closed, executing all registered finalizers.
54 *
55 * **Example** (Closing a scope)
56 *
57 * ```ts import.meta.vitest
58 * import { Effect, Exit, Scope } from "effect"
59 *
60 * const cleanups: Array<string> = []
61 * const program = Effect.gen(function*() {
62 * const scope = yield* Scope.make()
63 * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup!")))
64 * yield* Scope.close(scope, Exit.void)
65 * })
66 *
67 * Effect.runSync(program)
68 * cleanups // => ["Cleanup!"]
69 * ```
70 *
71 * @category models
72 * @since 2.0.0
73 */
74export interface Closeable extends Scope {
75 readonly [CloseableTypeId]: typeof CloseableTypeId
76}
77
78/**
79 * Scope states: `Empty` has no finalizers, `Open` has at least one, and
80 * `Closed` holds the exit value.
81 *
82 * **Example** (Checking scope states)
83 *
84 * ```ts import.meta.vitest
85 * import { Effect, Exit, Scope } from "effect"
86 *
87 * const program = Effect.gen(function*() {
88 * const scope = yield* Scope.make()
89 * const before = scope.state._tag
90 * yield* Scope.close(scope, Exit.void)
91 * return [before, scope.state._tag]
92 * })
93 *
94 * Effect.runSync(program) // => ["Empty", "Closed"]
95 * ```
96 *
97 * @since 4.0.0
98 */
99export declare namespace State {
100 /**
101 * Represents an open scope with no finalizers currently registered.
102 *
103 * **Details**
104 *
105 * Adding a finalizer moves it to `Open`; removing the last one returns it
106 * to `Empty`.
107 *
108 * **Example** (Inspecting an empty scope state)
109 *
110 * ```ts import.meta.vitest
111 * import { Scope } from "effect"
112 *
113 * const scope = Scope.makeUnsafe()
114 *
115 * scope.state._tag // => "Empty"
116 * ```
117 *
118 * @category models
119 * @since 4.0.0
120 */
121 export type Empty = {
122 readonly _tag: "Empty"
123 }
124 /**
125 * Represents an open scope state where finalizers can be added and
126 * the scope is still accepting new resources.
127 *
128 * **Details**
129 *
130 * Stores one finalizer inline and allocates the `finalizers` map when a
131 * second is added.
132 *
133 * **Example** (Inspecting an open scope state)
134 *
135 * ```ts import.meta.vitest
136 * import { Effect, Scope } from "effect"
137 *
138 * const scope = Scope.makeUnsafe()
139 *
140 * Effect.runSync(Scope.addFinalizer(scope, Effect.void))
141 * const state = scope.state
142 * if (state._tag !== "Open") throw new Error("unexpected state")
143 *
144 * state._tag // => "Open"
145 * state.finalizer !== undefined // => true
146 * ```
147 *
148 * @category models
149 * @since 4.0.0
150 */
151 export type Open = {
152 readonly _tag: "Open"
153 finalizerKey: {} | undefined
154 finalizer: ((exit: Exit<any, any>) => Effect<void>) | undefined
155 finalizers: Map<{}, (exit: Exit<any, any>) => Effect<void>> | undefined
156 }
157 /**
158 * Represents a closed scope state where finalizers have been executed
159 * and the scope is no longer accepting new resources.
160 *
161 * **Example** (Inspecting a closed scope state)
162 *
163 * ```ts import.meta.vitest
164 * import { Effect, Exit, Scope } from "effect"
165 *
166 * const program = Effect.gen(function*() {
167 * const scope = yield* Scope.make()
168 *
169 * yield* Scope.close(scope, Exit.succeed("Done"))
170 * if (scope.state._tag === "Closed") {
171 * return scope.state.exit
172 * }
173 * return Exit.die("unexpected state")
174 * })
175 *
176 * Effect.runSync(program) // => Exit.succeed("Done")
177 * ```
178 *
179 * @category models
180 * @since 4.0.0
181 */
182 export type Closed = {
183 readonly _tag: "Closed"
184 readonly exit: Exit<any, any>
185 }
186}
187
188/**
189 * Service tag for the active resource lifetime.
190 *
191 * **When to use**
192 *
193 * Use to access the active lifetime when registering finalizers or sharing
194 * resources with the surrounding scope.
195 *
196 * **Example** (Accessing the scope service)
197 *
198 * ```ts import.meta.vitest
199 * import { Effect, Scope } from "effect"
200 *
201 * const cleanups: Array<string> = []
202 * const program = Effect.gen(function*() {
203 * const scope = yield* Scope.Scope
204 * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup")))
205 * })
206 *
207 * Effect.runSync(Effect.scoped(program))
208 * cleanups // => ["Cleanup"]
209 * ```
210 *
211 * @category services
212 * @since 2.0.0
213 */
214export const Scope: Context.Service<Scope, Scope> = effect.scopeTag
215
216/**
217 * Creates a new `Scope` with the specified finalizer strategy.
218 *
219 * **Example** (Creating a scope)
220 *
221 * ```ts import.meta.vitest
222 * import { Effect, Exit, Scope } from "effect"
223 *
224 * const cleanups: Array<string> = []
225 * const program = Effect.gen(function*() {
226 * const scope = yield* Scope.make("sequential")
227 * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 1")))
228 * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup 2")))
229 * yield* Scope.close(scope, Exit.void)
230 * })
231 *
232 * Effect.runSync(program)
233 * cleanups // => ["Cleanup 2", "Cleanup 1"]
234 * ```
235 *
236 * @category constructors
237 * @since 2.0.0
238 */
239export const make: (finalizerStrategy?: "sequential" | "parallel") => Effect<Closeable> = effect.scopeMake
240
241/**
242 * Creates a new `Scope` synchronously without wrapping it in an `Effect`.
243 * This is useful when you need a scope immediately but should be used with caution
244 * as it doesn't provide the same safety guarantees as the `Effect`-wrapped version.
245 *
246 * **When to use**
247 *
248 * Use when a scope must be allocated synchronously and the caller will close it
249 * manually.
250 *
251 * **Example** (Creating a scope synchronously)
252 *
253 * ```ts import.meta.vitest
254 * import { Effect, Exit, Scope } from "effect"
255 *
256 * const scope = Scope.makeUnsafe("sequential")
257 * const cleanups: Array<string> = []
258 * const program = Effect.gen(function*() {
259 * yield* Scope.addFinalizer(scope, Effect.sync(() => cleanups.push("Cleanup")))
260 * yield* Scope.close(scope, Exit.void)
261 * })
262 *
263 * Effect.runSync(program)
264 * cleanups // => ["Cleanup"]
265 * ```
266 *
267 * @category constructors
268 * @since 4.0.0
269 */
270export const makeUnsafe: (finalizerStrategy?: "sequential" | "parallel") => Closeable = effect.scopeMakeUnsafe
271
272/**
273 * Provides a concrete `Scope` to an effect.
274 *
275 * **When to use**
276 *
277 * Use to run an effect that requires `Scope` with a scope managed by the
278 * caller.
279 *
280 * **Details**
281 *
282 * Providing the scope removes the `Scope` requirement from the effect context.
283 *
284 * **Example** (Providing a scope)
285 *
286 * ```ts import.meta.vitest
287 * import { Effect, Exit, Scope } from "effect"
288 *
289 * const events: Array<string> = []
290 * const program = Effect.gen(function*() {
291 * const scope = yield* Scope.Scope
292 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup")))
293 * events.push("working")
294 * })
295 *
296 * const withScope = Effect.gen(function*() {
297 * const scope = yield* Scope.make()
298 * yield* Scope.provide(scope)(program)
299 * yield* Scope.close(scope, Exit.void)
300 * })
301 *
302 * Effect.runSync(withScope)
303 * events // => ["working", "cleanup"]
304 * ```
305 *
306 * @category combinators
307 * @since 4.0.0
308 */
309export const provide: {
310 (value: Scope): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, Scope>>
311 <A, E, R>(self: Effect<A, E, R>, value: Scope): Effect<A, E, Exclude<R, Scope>>
312} = effect.provideScope
313
314/**
315 * Registers an exit-aware finalizer on a scope.
316 *
317 * **When to use**
318 *
319 * Use when cleanup needs to know whether the scope closed with success,
320 * failure, or interruption.
321 *
322 * **Details**
323 *
324 * If the scope is open, the finalizer runs when the scope closes and receives
325 * the scope's exit value. If the scope is already closed, the finalizer runs
326 * immediately with the stored exit value.
327 *
328 * **Example** (Adding an exit-aware finalizer)
329 *
330 * ```ts import.meta.vitest
331 * import { Effect, Exit, Scope } from "effect"
332 *
333 * const exits: Array<Exit.Exit<unknown, unknown>> = []
334 * const withResource = Effect.gen(function*() {
335 * const scope = yield* Scope.make()
336 * yield* Scope.addFinalizerExit(scope, (exit) => Effect.sync(() => exits.push(exit)))
337 * yield* Scope.close(scope, Exit.void)
338 * })
339 *
340 * Effect.runSync(withResource)
341 * exits // => [Exit.void]
342 * ```
343 *
344 * @category combinators
345 * @since 2.0.0
346 */
347export const addFinalizerExit: (scope: Scope, finalizer: (exit: Exit<any, any>) => Effect<unknown>) => Effect<void> =
348 effect.scopeAddFinalizerExit
349
350/**
351 * Registers a finalizer effect on a scope.
352 *
353 * **Details**
354 *
355 * If the scope is open, the finalizer runs when the scope closes, regardless of
356 * whether the scope closes successfully or with an error. If the scope is
357 * already closed, the finalizer runs immediately.
358 *
359 * **Example** (Adding finalizers)
360 *
361 * ```ts import.meta.vitest
362 * import { Effect, Exit, Scope } from "effect"
363 *
364 * const events: Array<string> = []
365 * const program = Effect.gen(function*() {
366 * const scope = yield* Scope.make()
367 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 1")))
368 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 2")))
369 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("cleanup 3")))
370 * events.push("work")
371 * yield* Scope.close(scope, Exit.void)
372 * })
373 *
374 * Effect.runSync(program)
375 * events // => ["work", "cleanup 3", "cleanup 2", "cleanup 1"]
376 * ```
377 *
378 * @category combinators
379 * @since 2.0.0
380 */
381export const addFinalizer: (scope: Scope, finalizer: Effect<unknown>) => Effect<void> = effect.scopeAddFinalizer
382
383/**
384 * Creates a closeable child scope registered with a parent scope.
385 *
386 * **Details**
387 *
388 * Closing the parent closes the child with the same exit value, and closing the
389 * child detaches it from the parent. The optional finalizer strategy configures
390 * the child scope and defaults to `"sequential"` when omitted.
391 *
392 * **Example** (Creating a child scope)
393 *
394 * ```ts import.meta.vitest
395 * import { Effect, Exit, Scope } from "effect"
396 *
397 * const cleanups: Array<string> = []
398 * const nestedScopes = Effect.gen(function*() {
399 * const parentScope = yield* Scope.make("sequential")
400 * yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent")))
401 * const childScope = yield* Scope.fork(parentScope, "parallel")
402 * yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child")))
403 * yield* Scope.close(childScope, Exit.void)
404 * yield* Scope.close(parentScope, Exit.void)
405 * })
406 *
407 * Effect.runSync(nestedScopes)
408 * cleanups // => ["child", "parent"]
409 * ```
410 *
411 * @category combinators
412 * @since 2.0.0
413 */
414export const fork: (
415 scope: Scope,
416 finalizerStrategy?: "sequential" | "parallel"
417) => Effect<Closeable> = effect.scopeFork
418
419/**
420 * Creates a closeable child scope synchronously and registers it with a parent scope.
421 *
422 * **When to use**
423 *
424 * Use when a child scope must be created synchronously and the caller controls
425 * both parent and child scope lifetimes.
426 *
427 * **Details**
428 *
429 * Closing the parent closes the child with the same exit value, and closing the
430 * child detaches it from the parent. The optional finalizer strategy configures
431 * the child scope and defaults to `"sequential"` when omitted.
432 *
433 * **Example** (Creating a child scope synchronously)
434 *
435 * ```ts import.meta.vitest
436 * import { Effect, Exit, Scope } from "effect"
437 *
438 * const cleanups: Array<string> = []
439 * const program = Effect.gen(function*() {
440 * const parentScope = Scope.makeUnsafe("sequential")
441 * const childScope = Scope.forkUnsafe(parentScope, "parallel")
442 * yield* Scope.addFinalizer(parentScope, Effect.sync(() => cleanups.push("parent")))
443 * yield* Scope.addFinalizer(childScope, Effect.sync(() => cleanups.push("child")))
444 * yield* Scope.close(childScope, Exit.void)
445 * yield* Scope.close(parentScope, Exit.void)
446 * })
447 *
448 * Effect.runSync(program)
449 * cleanups // => ["child", "parent"]
450 * ```
451 *
452 * @category combinators
453 * @since 4.0.0
454 */
455export const forkUnsafe: (scope: Scope, finalizerStrategy?: "sequential" | "parallel") => Closeable =
456 effect.scopeForkUnsafe
457
458/**
459 * Closes a scope and runs its registered finalizers.
460 *
461 * **When to use**
462 *
463 * Use to close a scope manually with a specific exit value.
464 *
465 * **Details**
466 *
467 * Finalizers run in the scope's configured order and receive the supplied
468 * `Exit`.
469 * By default, finalizers run uninterruptibly, so interrupting the closing fiber
470 * waits for them to finish; a finalizer can explicitly restore interruptibility.
471 *
472 * **Example** (Running scope finalizers)
473 *
474 * ```ts import.meta.vitest
475 * import { Effect, Exit, Scope } from "effect"
476 *
477 * const events: Array<string> = []
478 * const resourceManagement = Effect.gen(function*() {
479 * const scope = yield* Scope.make("sequential")
480 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("database")))
481 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("file")))
482 * yield* Scope.addFinalizer(scope, Effect.sync(() => events.push("memory")))
483 * events.push("work")
484 * yield* Scope.close(scope, Exit.succeed("Success!"))
485 * })
486 *
487 * Effect.runSync(resourceManagement)
488 * events // => ["work", "memory", "file", "database"]
489 * ```
490 *
491 * @category combinators
492 * @since 2.0.0
493 */
494export const close: <A, E>(self: Closeable, exit: Exit<A, E>) => Effect<void> = effect.scopeClose
495
496/**
497 * Closes a scope unsafely with the provided exit value.
498 *
499 * **When to use**
500 *
501 * Use when implementing lower-level scope machinery that must transition a
502 * scope to `Closed` immediately and can run the returned finalizer effect when
503 * one is produced.
504 *
505 * **Details**
506 *
507 * Returns an effect that runs registered finalizers, or `undefined` when the
508 * scope was already closed or no finalizers need to run.
509 *
510 * **Gotchas**
511 *
512 * Ignoring the returned effect skips registered finalizers. The caller must
513 * run the returned effect uninterruptibly: the scope is already closed, so
514 * interruption during finalization can permanently skip remaining finalizers.
515 *
516 * @see {@link close} for the usual effectful close operation that always returns an `Effect`
517 *
518 * @category unsafe
519 * @since 4.0.0
520 */
521export const closeUnsafe: <A, E>(self: Closeable, exit_: Exit<A, E>) => Effect<void, never, never> | undefined =
522 effect.scopeCloseUnsafe
523
524/**
525 * Runs an effect with the provided closeable scope in its context and closes
526 * that scope when the effect exits.
527 *
528 * **When to use**
529 *
530 * Use when you already have a `Closeable` scope and want to run an effect that
531 * requires `Scope` while automatically closing that scope when the effect exits.
532 *
533 * **Details**
534 *
535 * The scope is closed with the same exit value as the effect, so registered
536 * finalizers can observe whether the effect succeeded, failed, or was
537 * interrupted.
538 *
539 * @see `provide` for providing a scope without closing it automatically
540 * @see `Effect.scoped` for creating and closing a fresh scope around a workflow
541 *
542 * @category combinators
543 * @since 2.0.0
544 */
545export const use: {
546 (scope: Closeable): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, Scope>>
547 <A, E, R>(self: Effect<A, E, R>, scope: Closeable): Effect<A, E, Exclude<R, Scope>>
548} = effect.scopeUse