Hyperlinkv0.8.0-beta.28

Stream

Stream.withExecutionPlanconsteffect/Stream.ts:6216
<Input, R2, Provides, PolicyE>(
  policy: ExecutionPlan.ExecutionPlan<{
    provides: Provides
    input: Input
    error: PolicyE
    requirements: R2
  }>,
  options?: {
    readonly preventFallbackOnPartialStream?: boolean | undefined
  }
): <A, E extends Input, R>(
  self: Stream<A, E, R>
) => Stream<A, E | PolicyE, R2 | Exclude<R, Provides>>
<A, E extends Input, R, R2, Input, Provides, PolicyE>(
  self: Stream<A, E, R>,
  policy: ExecutionPlan.ExecutionPlan<{
    provides: Provides
    input: Input
    error: PolicyE
    requirements: R2
  }>,
  options?: {
    readonly preventFallbackOnPartialStream?: boolean | undefined
  }
): Stream<A, E | PolicyE, R2 | Exclude<R, Provides>>

Applies an ExecutionPlan to a stream, retrying with step-provided resources until it succeeds or the plan is exhausted.

Details

By default, a failing step can fallback even after emitting elements; set preventFallbackOnPartialStream to fail instead of mixing partial output with a later fallback.

Example (Applying an execution plan)

import { Console, Context, Effect, ExecutionPlan, Layer, Stream } from "effect"

class Service extends Context.Service<Service>()("Service", {
  make: Effect.succeed({
    stream: Stream.fail("A") as Stream.Stream<number, string>
  })
}) {
  static Bad = Layer.succeed(Service, Service.of({ stream: Stream.fail("A") }))
  static Good = Layer.succeed(Service, Service.of({ stream: Stream.make(1, 2, 3) }))
}

const plan = ExecutionPlan.make(
  { provide: Service.Bad },
  { provide: Service.Good }
)

const stream = Stream.unwrap(Effect.map(Service, (_) => _.stream))

const program = Effect.gen(function*() {
  const items = yield* stream.pipe(Stream.withExecutionPlan(plan), Stream.runCollect)
  yield* Console.log(items)
})

Effect.runPromise(program)
// Output: [ 1, 2, 3 ]
error handling
Source effect/Stream.ts:621688 lines
export const withExecutionPlan: {
  <Input, R2, Provides, PolicyE>(
    policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>,
    options?: { readonly preventFallbackOnPartialStream?: boolean | undefined }
  ): <A, E extends Input, R>(self: Stream<A, E, R>) => Stream<A, E | PolicyE, R2 | Exclude<R, Provides>>
  <A, E extends Input, R, R2, Input, Provides, PolicyE>(
    self: Stream<A, E, R>,
    policy: ExecutionPlan.ExecutionPlan<{ provides: Provides; input: Input; error: PolicyE; requirements: R2 }>,
    options?: { readonly preventFallbackOnPartialStream?: boolean | undefined }
  ): Stream<A, E | PolicyE, R2 | Exclude<R, Provides>>
} = dual((args) => isStream(args[0]), <A, E extends Input, R, R2, Input, Provides, PolicyE>(
  self: Stream<A, E, R>,
  policy: ExecutionPlan.ExecutionPlan<{
    provides: Provides
    input: Input
    error: PolicyE
    requirements: R2
  }>,
  options?: {
    readonly preventFallbackOnPartialStream?: boolean | undefined
  }
): Stream<A, E | PolicyE, R2 | Exclude<R, Provides>> =>
  suspend(() => {
    const preventFallbackOnPartialStream = options?.preventFallbackOnPartialStream ?? false
    let i = 0
    let meta: ExecutionPlan.Metadata = {
      attempt: 0,
      stepIndex: 0
    }
    const provideMeta = provideServiceEffect(
      ExecutionPlan.CurrentMetadata,
      Effect.sync(() => {
        meta = {
          attempt: meta.attempt + 1,
          stepIndex: i
        }
        return meta
      })
    )
    let lastError = Option.none<E | PolicyE>()
    const loop: Stream<
      A,
      E | PolicyE,
      R2 | Exclude<R, Provides>
    > = suspend(() => {
      const step = policy.steps[i]
      if (!step) {
        return fail(Option.getOrThrow(lastError))
      }

      let nextStream: Stream<A, E | PolicyE, R2 | Exclude<R, Provides>> = provideMeta(provide(self, step.provide))
      let receivedElements = false

      if (Option.isSome(lastError)) {
        const error = lastError.value
        let attempted = false
        const wrapped = nextStream
        // ensure the schedule is applied at least once
        nextStream = suspend(() => {
          if (attempted) return wrapped
          attempted = true
          return fail(error)
        })
        nextStream = retry(nextStream, internalExecutionPlan.scheduleFromStep(step, false) as any)
      } else {
        const schedule = internalExecutionPlan.scheduleFromStep(step, true)
        nextStream = schedule ? retry(nextStream, schedule as any) : nextStream
      }

      return catch_(
        preventFallbackOnPartialStream ?
          onFirst(nextStream, (_) => {
            receivedElements = true
            return Effect.void
          }) :
          nextStream,
        (error) => {
          i++
          if (preventFallbackOnPartialStream && receivedElements) {
            return fail(error)
          }
          lastError = Option.some(error)
          return loop
        }
      )
    })
    return loop
  }))