1/**
2 * Models one low-level pull step for stream-like consumers.
3 *
4 * A `Pull<A, E, Done, R>` is an `Effect` that can produce one `A`, fail with an
5 * ordinary error `E`, or signal end-of-input with `Cause.Done<Done>`. The
6 * separate done signal lets low-level consumers distinguish normal completion
7 * from failure. This module includes type extractors and helpers for detecting,
8 * filtering, catching, converting, and matching done signals separately from
9 * ordinary failures.
10 *
11 * @since 4.0.0
12 */
13import * as Cause from "./Cause.ts"
14import type { Effect } from "./Effect.ts"
15import * as Exit from "./Exit.ts"
16import * as Filter from "./Filter.ts"
17import { dual } from "./Function.ts"
18import * as internalEffect from "./internal/effect.ts"
19import * as Result from "./Result.ts"
20
21/**
22 * An effectful pull step that either produces a value, fails with `E`, or
23 * signals completion with `Cause.Done<Done>`.
24 *
25 * **When to use**
26 *
27 * Use to model one low-level pull step when a consumer repeatedly evaluates an
28 * effect that may emit a value, fail normally, or signal normal completion
29 * through `Cause.Done`.
30 *
31 * **Details**
32 *
33 * `Pull` represents completion in the error channel so low-level stream
34 * consumers can distinguish ordinary failures from end-of-input and carry a
35 * leftover value when needed.
36 *
37 * @category models
38 * @since 4.0.0
39 */
40export interface Pull<out A, out E = never, out Done = void, out R = never>
41 extends Effect<A, E | Cause.Done<Done>, R>
42{}
43
44/**
45 * Extracts the success type from a Pull type.
46 *
47 * **When to use**
48 *
49 * Use to derive the value produced by an existing `Pull` when declaring
50 * reusable type aliases, low-level stream helpers, or function signatures.
51 *
52 * @see {@link Error} for extracting the ordinary failure type
53 * @see {@link Leftover} for extracting the completion leftover type
54 * @see {@link Services} for extracting the required services type instead
55 *
56 * @category utility types
57 * @since 4.0.0
58 */
59export type Success<P> = P extends Effect<infer _A, infer _E, infer _R> ? _A : never
60
61/**
62 * Extracts the error type from a Pull type, excluding Done errors.
63 *
64 * **When to use**
65 *
66 * Use to derive only the ordinary failure type from a `Pull` when declaring
67 * wrappers or APIs that handle completion separately.
68 *
69 * @see {@link Success} for extracting the pulled value type instead
70 * @see {@link Leftover} for extracting the completion leftover type
71 * @see {@link Services} for extracting the required services type instead
72 * @see {@link ExcludeDone} for excluding `Cause.Done` from an error union
73 *
74 * @category utility types
75 * @since 4.0.0
76 */
77export type Error<P> = P extends Effect<infer _A, infer _E, infer _R> ? _E extends Cause.Done<infer _L> ? never : _E
78 : never
79
80/**
81 * Extracts the leftover type from a Pull type.
82 *
83 * **When to use**
84 *
85 * Use to derive the completion leftover type from an existing `Pull` when
86 * declaring reusable type aliases or helper signatures that preserve a pull's
87 * done value.
88 *
89 * @see {@link Success} for extracting the pulled value type instead
90 * @see {@link Error} for extracting the ordinary failure type, excluding `Cause.Done`
91 * @see {@link Services} for extracting the required services type instead
92 *
93 * @category utility types
94 * @since 4.0.0
95 */
96export type Leftover<P> = P extends Effect<infer _A, infer _E, infer _R> ? _E extends Cause.Done<infer _L> ? _L : never
97 : never
98
99/**
100 * Extracts the service requirements (context) type from a Pull type.
101 *
102 * **When to use**
103 *
104 * Use to derive the context requirements of a generic or inferred `Pull`
105 * without restating its `R` type parameter.
106 *
107 * @see {@link Success} for extracting the pulled value type instead
108 * @see {@link Error} for extracting the ordinary failure type
109 * @see {@link Leftover} for extracting the completion leftover type
110 *
111 * @category utility types
112 * @since 4.0.0
113 */
114export type Services<P> = P extends Effect<infer _A, infer _E, infer _R> ? _R : never
115
116/**
117 * Excludes `Cause.Done` completion signals from an error type union.
118 *
119 * **When to use**
120 *
121 * Use to describe the ordinary error type that remains after `Cause.Done`
122 * completion signals have been handled or filtered out of an error union.
123 *
124 * @see {@link Error} for extracting ordinary failures from a `Pull`
125 * @see {@link Leftover} for extracting the completion leftover type
126 *
127 * @category utility types
128 * @since 4.0.0
129 */
130export type ExcludeDone<E> = Exclude<E, Cause.Done<any>>
131
132// -----------------------------------------------------------------------------
133// Done
134// -----------------------------------------------------------------------------
135
136/**
137 * Handles `Cause.Done` failures in an effect while leaving ordinary failures
138 * in the error channel.
139 *
140 * **When to use**
141 *
142 * Use to recover from a `Cause.Done` completion signal in an effect, such as
143 * turning a pull leftover value into a successful recovery effect while
144 * preserving ordinary failures.
145 *
146 * **Details**
147 *
148 * The handler receives the done leftover value and may recover with a new
149 * effect. Non-done errors are preserved.
150 *
151 * @see {@link matchEffect} for handling success, ordinary failure, and done outcomes explicitly
152 * @see {@link filterDoneLeftover} for extracting a done leftover from an existing `Cause`
153 *
154 * @category error handling
155 * @since 4.0.0
156 */
157export const catchDone: {
158 <E, A2, E2, R2>(f: (leftover: Cause.Done.Extract<E>) => Effect<A2, E2, R2>): <A, R>(
159 self: Effect<A, E, R>
160 ) => Effect<A | A2, ExcludeDone<E> | E2, R | R2>
161 <A, R, E, A2, E2, R2>(
162 self: Effect<A, E, R>,
163 f: (leftover: Cause.Done.Extract<E>) => Effect<A2, E2, R2>
164 ): Effect<A | A2, ExcludeDone<E> | E2, R | R2>
165} = dual(2, <A, R, E, A2, E2, R2>(
166 effect: Effect<A, E, R>,
167 f: (leftover: Cause.Done.Extract<E>) => Effect<A2, E2, R2>
168): Effect<A | A2, ExcludeDone<E> | E2, R | R2> =>
169 internalEffect.catchCauseFilter(effect, filterDoneLeftover as any, (l: any) => f(l)) as any)
170
171/**
172 * Checks whether a Cause contains any done errors.
173 *
174 * **When to use**
175 *
176 * Use when you need to test whether a pull failure cause represents normal
177 * completion and only need a boolean result.
178 *
179 * @see {@link isDoneFailure} for checking a single `Cause.Reason`
180 * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause`
181 * @see {@link filterNoDone} for selecting causes with no done failures
182 *
183 * @category predicates
184 * @since 4.0.0
185 */
186export const isDoneCause = <E>(cause: Cause.Cause<E>): boolean => cause.reasons.some(isDoneFailure)
187
188/**
189 * Checks whether a `Cause.Reason` is a `Fail` reason whose error is a
190 * `Cause.Done` signal.
191 *
192 * **When to use**
193 *
194 * Use when you need to identify done completion reasons while traversing
195 * `cause.reasons`, before handling ordinary failures.
196 *
197 * @see {@link isDoneCause} for checking an entire `Cause` for any done reason
198 * @see {@link filterDone} for extracting the `Cause.Done` value from a `Cause`
199 *
200 * @category guards
201 * @since 4.0.0
202 */
203export const isDoneFailure = <E>(
204 failure: Cause.Reason<E>
205): failure is Cause.Fail<E & Cause.Done<any>> => failure._tag === "Fail" && Cause.isDone(failure.error)
206
207/**
208 * Finds a `Cause.Done` failure in a `Cause`.
209 *
210 * **When to use**
211 *
212 * Use to separate `Cause.Done` completion from ordinary causes while preserving
213 * the typed done value.
214 *
215 * **Details**
216 *
217 * Returns a successful `Result` with the `Cause.Done` value when the cause
218 * contains a done signal and no other failures besides interruptions. When the
219 * done signal was merged with a real failure (for example a failing
220 * finalizer), the `Result` fails with the remaining cause, stripped of the
221 * done signal. Without a done signal the `Result` fails with the original
222 * cause.
223 *
224 * @category filtering
225 * @since 4.0.0
226 */
227export const filterDone: <E>(
228 input: Cause.Cause<E>
229) => Result.Result<Cause.Done.Only<E>, Cause.Cause<ExcludeDone<E>>> = <E>(
230 cause: Cause.Cause<E>
231): Result.Result<any, any> => {
232 let done: Cause.Done<any> | undefined
233 let hasFailure = false
234 for (const reason of cause.reasons) {
235 if (isDoneFailure(reason)) {
236 done ??= reason.error
237 } else if (reason._tag !== "Interrupt") {
238 hasFailure = true
239 }
240 }
241 if (done === undefined) return Result.fail(cause)
242 return hasFailure
243 ? Result.fail(Cause.fromReasons(cause.reasons.filter((reason) => !isDoneFailure(reason))))
244 : Result.succeed(done)
245}
246
247/**
248 * Finds a `Cause.Done` failure in a cause whose done value is not used.
249 *
250 * **When to use**
251 *
252 * Use to detect `Cause.Done` completion in a `Cause` when the completion value
253 * is not part of the downstream logic.
254 *
255 * **Details**
256 *
257 * Returns a successful `Result` with the done marker when it is the only
258 * failure, otherwise returns a failed `Result` with the non-done cause.
259 *
260 * @see {@link filterDone} for preserving the typed `Cause.Done` value when the done payload matters
261 * @see {@link filterDoneLeftover} for extracting only the done leftover value
262 * @see {@link filterNoDone} for the inverse filter that succeeds only when no done failure is present
263 *
264 * @category filtering
265 * @since 4.0.0
266 */
267export const filterDoneVoid: <E extends Cause.Done>(
268 input: Cause.Cause<E>
269) => Result.Result<Cause.Done, Cause.Cause<Exclude<E, Cause.Done>>> = filterDone as any
270
271/**
272 * Keeps a `Cause` only when it contains no `Cause.Done` failures.
273 *
274 * **When to use**
275 *
276 * Use to select ordinary failure causes for handling while leaving `Cause.Done`
277 * completion causes outside that handler.
278 *
279 * **Details**
280 *
281 * Returns a successful `Result` with the cause when every failure is non-done;
282 * otherwise returns a failed `Result` with the original cause.
283 *
284 * @see {@link filterDone} for the inverse typed done filter
285 * @see {@link filterDoneVoid} for done detection when the payload is not needed
286 *
287 * @category filtering
288 * @since 4.0.0
289 */
290export const filterNoDone: <E>(
291 input: Cause.Cause<E>
292) => Result.Result<
293 Cause.Cause<ExcludeDone<E>>,
294 Cause.Cause<E>
295> = Filter.fromPredicate((cause: Cause.Cause<unknown>) =>
296 cause.reasons.every((failure) => !isDoneFailure(failure))
297) as any
298
299/**
300 * Filters a Cause to extract the leftover value from done errors.
301 *
302 * **When to use**
303 *
304 * Use to extract only the leftover value carried by a `Cause.Done` completion
305 * signal.
306 *
307 * @category filtering
308 * @since 4.0.0
309 */
310export const filterDoneLeftover: <E>(
311 cause: Cause.Cause<E>
312) => Result.Result<Cause.Done.Extract<E>, Cause.Cause<ExcludeDone<E>>> = ((cause: Cause.Cause<any>) => {
313 const done = filterDone(cause)
314 return Result.isFailure(done) ? done : Result.succeed(done.success.value)
315}) as any
316
317/**
318 * Converts a `Cause` into an `Exit`, treating `Cause.Done` as successful
319 * completion.
320 *
321 * **When to use**
322 *
323 * Use to produce an `Exit` for finalizing a low-level pull workflow when a
324 * `Cause.Done` signal should be treated as success and any remaining cause
325 * should fail.
326 *
327 * **Details**
328 *
329 * If the done signal is the only failure in the cause, its leftover becomes
330 * the successful value. Otherwise the non-done cause becomes the failure
331 * cause.
332 *
333 * @see {@link filterDone} for extracting the done signal without converting the cause to an `Exit`
334 * @see {@link matchEffect} for handling `Pull` success, failure, and done outcomes directly
335 *
336 * @category converting
337 * @since 4.0.0
338 */
339export const doneExitFromCause = <E>(cause: Cause.Cause<E>): Exit.Exit<Cause.Done.Extract<E>, ExcludeDone<E>> => {
340 const halt = filterDone(cause)
341 return !Result.isFailure(halt) ? Exit.succeed(halt.success.value as any) : Exit.failCause(halt.failure)
342}
343
344/**
345 * Pattern matches on a Pull, handling success, failure, and done cases.
346 *
347 * **When to use**
348 *
349 * Use to handle all three `Pull` outcomes with effectful handlers.
350 *
351 * **Example** (Matching Pull outcomes)
352 *
353 * ```ts import.meta.vitest
354 * import { Cause, Effect, Pull } from "effect"
355 *
356 * const pull = Cause.done("stream ended")
357 *
358 * const result = Pull.matchEffect(pull, {
359 * onSuccess: (value) => Effect.succeed(`Got value: ${value}`),
360 * onFailure: (cause) => Effect.succeed(`Got error: ${cause}`),
361 * onDone: (leftover) => Effect.succeed(`Stream halted with: ${leftover}`)
362 * })
363 *
364 * await Effect.runPromise(result) // => "Stream halted with: stream ended"
365 * ```
366 *
367 * @category pattern matching
368 * @since 4.0.0
369 */
370export const matchEffect: {
371 <A, E, L, AS, ES, RS, AF, EF, RF, AH, EH, RH>(options: {
372 readonly onSuccess: (value: A) => Effect<AS, ES, RS>
373 readonly onFailure: (failure: Cause.Cause<E>) => Effect<AF, EF, RF>
374 readonly onDone: (leftover: L) => Effect<AH, EH, RH>
375 }): <R>(self: Pull<A, E, L, R>) => Effect<AS | AF | AH, ES | EF | EH, R | RS | RF | RH>
376 <A, E, L, R, AS, ES, RS, AF, EF, RF, AH, EH, RH>(self: Pull<A, E, L, R>, options: {
377 readonly onSuccess: (value: A) => Effect<AS, ES, RS>
378 readonly onFailure: (failure: Cause.Cause<E>) => Effect<AF, EF, RF>
379 readonly onDone: (leftover: L) => Effect<AH, EH, RH>
380 }): Effect<AS | AF | AH, ES | EF | EH, R | RS | RF | RH>
381} = dual(2, <A, E, L, R, AS, ES, RS, AF, EF, RF, AH, EH, RH>(self: Pull<A, E, L, R>, options: {
382 readonly onSuccess: (value: A) => Effect<AS, ES, RS>
383 readonly onFailure: (failure: Cause.Cause<E>) => Effect<AF, EF, RF>
384 readonly onDone: (leftover: L) => Effect<AH, EH, RH>
385}): Effect<AS | AF | AH, ES | EF | EH, R | RS | RF | RH> =>
386 internalEffect.matchCauseEffect(self, {
387 onSuccess: options.onSuccess,
388 onFailure: (cause): Effect<AS | AF | AH, ES | EF | EH, RS | RF | RH> => {
389 const halt = filterDone(cause)
390 return !Result.isFailure(halt) ? options.onDone(halt.success.value as L) : options.onFailure(halt.failure)
391 }
392 }))