1/**
2 * Stores refreshable scoped values.
3 *
4 * A `Resource<A, E>` keeps the latest successful or failed acquisition result.
5 * It can be read repeatedly, refreshed manually, or refreshed automatically on a
6 * schedule. Resource acquisition runs in a scope, so replacements and final
7 * cleanup release the resources owned by previous values.
8 *
9 * @since 2.0.0
10 */
11import * as Context from "./Context.ts"
12import * as Effect from "./Effect.ts"
13import * as Exit from "./Exit.ts"
14import { identity } from "./Function.ts"
15import { PipeInspectableProto } from "./internal/core.ts"
16import type { Pipeable } from "./Pipeable.ts"
17import { hasProperty } from "./Predicate.ts"
18import type * as Schedule from "./Schedule.ts"
19import type * as Scope from "./Scope.ts"
20import * as ScopedRef from "./ScopedRef.ts"
21
22const TypeId = "~effect/Resource" as const
23
24/**
25 * A `Resource` is a value loaded into memory that can be refreshed manually or
26 * automatically according to a schedule.
27 *
28 * **When to use**
29 *
30 * Use to model a scoped value whose latest acquisition result is kept available
31 * for repeated reads and can be refreshed manually or on a schedule.
32 *
33 * @see {@link manual} for creating a resource refreshed by the caller
34 * @see {@link auto} for creating a resource refreshed according to a schedule
35 * @see {@link get} for reading the currently stored acquisition result
36 * @see {@link refresh} for forcing a new acquisition
37 *
38 * @category models
39 * @since 2.0.0
40 */
41export interface Resource<in out A, in out E = never> extends Pipeable {
42 readonly [TypeId]: typeof TypeId
43 readonly scopedRef: ScopedRef.ScopedRef<Exit.Exit<A, E>>
44 readonly acquire: Effect.Effect<A, E>
45}
46
47/**
48 * Returns `true` if the specified value is a `Resource`.
49 *
50 * **When to use**
51 *
52 * Use to validate unknown values at runtime boundaries before treating them as
53 * `Resource` values.
54 *
55 * **Details**
56 *
57 * This predicate narrows the input to `Resource<unknown, unknown>`.
58 *
59 * @category guards
60 * @since 4.0.0
61 */
62export const isResource: (u: unknown) => u is Resource<unknown, unknown> = (
63 u: unknown
64): u is Resource<unknown, unknown> => hasProperty(u, TypeId)
65
66const Proto = {
67 ...PipeInspectableProto,
68 [TypeId]: TypeId,
69 toJSON() {
70 return {
71 _id: "Resource"
72 }
73 }
74}
75
76const makeUnsafe = <A, E>(
77 scopedRef: ScopedRef.ScopedRef<Exit.Exit<A, E>>,
78 acquire: Effect.Effect<A, E>
79): Resource<A, E> => {
80 const self = Object.create(Proto)
81 self.scopedRef = scopedRef
82 self.acquire = acquire
83 return self
84}
85
86/**
87 * Creates a `Resource` that must be refreshed manually.
88 *
89 * **When to use**
90 *
91 * Use when you need manual control over resource refresh timing rather than an
92 * automatic schedule.
93 *
94 * @see {@link auto} for schedule-driven automatic refreshes
95 * @see {@link refresh} to manually trigger a resource refresh
96 * @category constructors
97 * @since 2.0.0
98 */
99export const manual = <A, E, R>(
100 acquire: Effect.Effect<A, E, R>
101): Effect.Effect<Resource<A, E>, never, Scope.Scope | R> =>
102 Effect.contextWith((context: Context.Context<R>) => {
103 const providedAcquire = Effect.updateContext(
104 acquire,
105 (input: Context.Context<never>) => Context.merge(context, input)
106 )
107 return Effect.map(
108 ScopedRef.fromAcquire(Effect.exit(providedAcquire)),
109 (scopedRef) => makeUnsafe(scopedRef, providedAcquire)
110 )
111 })
112
113/**
114 * Creates a `Resource` that refreshes automatically according to the supplied
115 * schedule.
116 *
117 * **When to use**
118 *
119 * Use when a resource should refresh in the background according to a schedule
120 * for the lifetime of its scope.
121 *
122 * @see {@link manual} for caller-controlled refresh timing
123 * @see {@link refresh} to trigger a refresh explicitly
124 *
125 * @category constructors
126 * @since 2.0.0
127 */
128export const auto = <A, E, R, Out, E2, R2>(
129 acquire: Effect.Effect<A, E, R>,
130 policy: Schedule.Schedule<Out, unknown, E2, R2>
131): Effect.Effect<Resource<A, E>, never, R | R2 | Scope.Scope> =>
132 Effect.tap(
133 manual(acquire),
134 (self) => Effect.forkScoped(Effect.repeat(refresh(self), policy))
135 )
136
137/**
138 * Retrieves the current value stored in this resource.
139 *
140 * **When to use**
141 *
142 * Use to read the value currently cached by a `Resource`.
143 *
144 * **Gotchas**
145 *
146 * If the resource currently stores a failed acquisition result, the returned
147 * effect fails with the stored error.
148 *
149 * @see {@link refresh} to re-run acquisition and update the stored value before a later read
150 *
151 * @category getters
152 * @since 2.0.0
153 */
154export const get = <A, E>(self: Resource<A, E>): Effect.Effect<A, E> =>
155 Effect.flatMap(ScopedRef.get(self.scopedRef), identity)
156
157/**
158 * Re-runs this resource's acquisition effect and updates the current value.
159 *
160 * **When to use**
161 *
162 * Use to force an existing `Resource` to reacquire its value at a
163 * caller-controlled point.
164 *
165 * **Details**
166 *
167 * When acquisition succeeds, refreshing replaces the value stored in the
168 * resource's scoped reference and releases resources associated with the
169 * previous value.
170 *
171 * **Gotchas**
172 *
173 * If acquisition fails, the returned effect fails and the previously stored
174 * result is left as what `get` reads.
175 *
176 * @see {@link get} for reading the current stored value
177 * @see {@link manual} for resources refreshed only by caller action
178 * @see {@link auto} for schedule-driven automatic refreshes
179 *
180 * @category resource management
181 * @since 2.0.0
182 */
183export const refresh = <A, E>(self: Resource<A, E>): Effect.Effect<void, E> =>
184 ScopedRef.set(self.scopedRef, Effect.map(self.acquire, Exit.succeed))