import * as Arr from "../Array.ts" import type * as Cause from "../Cause.ts" import type * as Clock from "../Clock.ts" import type * as Console from "../Console.ts" import * as Context from "../Context.ts" import * as Duration from "../Duration.ts" import type * as Effect from "../Effect.ts" import * as Equal from "../Equal.ts" import type * as Exit from "../Exit.ts" import type * as Fiber from "../Fiber.ts" import * as Filter from "../Filter.ts" import { formatJson } from "../Formatter.ts" import type { LazyArg } from "../Function.ts" import { constant, constFalse, constTrue, constUndefined, constVoid, dual, identity } from "../Function.ts" import * as Hash from "../Hash.ts" import { toJson, toStringUnknown } from "../Inspectable.ts" import * as Iterable from "../Iterable.ts" import type * as _Latch from "../Latch.ts" import type * as Logger from "../Logger.ts" import type * as LogLevel from "../LogLevel.ts" import * as Option from "../Option.ts" import * as Order from "../Order.ts" import { pipeArguments } from "../Pipeable.ts" import type * as Predicate from "../Predicate.ts" import { hasProperty, isIterable, isString, isTagged } from "../Predicate.ts" import { currentFiberTypeId, redact } from "../Redactable.ts" import type { StackFrame } from "../References.ts" import * as Result from "../Result.ts" import * as Scheduler from "../Scheduler.ts" import type * as Scope from "../Scope.ts" import * as Tracer from "../Tracer.ts" import type { Concurrency, EqualsWith, ExcludeReason, ExcludeTag, ExtractReason, ExtractTag, NarrowReason, NoInfer, OmitReason, ReasonOf, ReasonTags, Simplify, Tags, unassigned } from "../Types.ts" import { internalCall } from "../Utils.ts" import type { Primitive } from "./core.ts" import { args, causeAddInterrupts, causeAnnotate, causeCombine, causeEmpty, causeFromReasons, CauseImpl, constEmptyAnnotations, contA, contAll, contE, Die, evaluate, exitDie, exitFail, exitFailCause, exitSucceed, ExitTypeId, Fail, InterruptorStackTrace, isCause, isDieReason, isDone, isEffect, isFailReason, isInterruptReason, isNoSuchElementError, makePrimitive, makePrimitiveProto, NoSuchElementError, ReasonBase, StackTraceKey as CauseStackTrace, TaggedError, withFiber, Yield } from "./core.ts" import * as doNotation from "./doNotation.ts" import * as InternalMetric from "./metric.ts" import * as InternalRecord from "./record.ts" import { CurrentErrorReporters, CurrentLogAnnotations, CurrentLogLevel, CurrentLogSpans, CurrentStackFrame, MinimumLogLevel, TracerEnabled, TracerSpanAnnotations, TracerSpanLinks, TracerTimingEnabled } from "./references.ts" import { getStackTraceLimit, setStackTraceLimit } from "./stackTraceLimit.ts" import { addSpanStackTrace, makeStackCleaner } from "./tracer.ts" // ---------------------------------------------------------------------------- // Cause // ---------------------------------------------------------------------------- /** @internal */ export class Interrupt extends ReasonBase<"Interrupt"> implements Cause.Interrupt { declare readonly fiberId: number | undefined constructor( fiberId: number | undefined, annotations = constEmptyAnnotations ) { super("Interrupt", annotations, "Interrupted") this.fiberId = fiberId } override toString() { return `Interrupt(${this.fiberId})` } toJSON(): unknown { return { _tag: "Interrupt", fiberId: this.fiberId } } [Equal.symbol](that: any): boolean { return ( isInterruptReason(that) && this.fiberId === that.fiberId && this.annotations === that.annotations ) } [Hash.symbol](): number { // Equality compares annotations by reference, so leave them out instead of // hashing them randomly. return Hash.string(`${this._tag}:${this.fiberId}`) } } /** @internal */ export const makeInterruptReason = (fiberId?: number | undefined): Cause.Interrupt => new Interrupt(fiberId) /** @internal */ export const causeInterrupt = ( fiberId?: number | undefined ): Cause.Cause => new CauseImpl([new Interrupt(fiberId)]) /** @internal */ export const hasFails = (self: Cause.Cause): boolean => self.reasons.some(isFailReason) /** @internal */ export const findFail = (self: Cause.Cause): Result.Result, Cause.Cause> => { const reason = self.reasons.find(isFailReason) return reason ? Result.succeed(reason) : Result.fail(self as Cause.Cause) } /** @internal */ export const findError = (self: Cause.Cause): Result.Result> => { for (let i = 0; i < self.reasons.length; i++) { const reason = self.reasons[i] if (reason._tag === "Fail") { return Result.succeed(reason.error) } } return Result.fail(self as Cause.Cause) } /** @internal */ export const findErrorOption = Filter.toOption(findError) /** @internal */ export const hasDies = (self: Cause.Cause): boolean => self.reasons.some(isDieReason) /** * Defects take precedence over typed failures when causes are combined: if * the cause contains a `Die`, every `Fail` becomes a `Die`. `Done` signals * stay typed so pulls still complete. * * @internal */ export const causeElectDefects = (self: Cause.Cause): Cause.Cause => hasDies(self) && hasFails(self) ? causeFromReasons( self.reasons.map((reason) => reason._tag === "Fail" && !isDone(reason.error) ? new Die(reason.error, reason.annotations) : reason ) ) : self /** * Selects the first typed error unless a defect is present, in which case * every `Fail` is elected to `Die`. Interrupts do not block typed recovery. * * @internal */ export const findRecoverableError = (self: Cause.Cause): Result.Result> => hasDies(self) ? Result.fail(causeElectDefects(self) as Cause.Cause) : findError(self) /** @internal */ export const findDie = (self: Cause.Cause): Result.Result> => { const reason = self.reasons.find(isDieReason) return reason ? Result.succeed(reason) : Result.fail(self) } /** @internal */ export const findDefect = (self: Cause.Cause): Result.Result> => { const reason = self.reasons.find(isDieReason) return reason ? Result.succeed(reason.defect) : Result.fail(self) } /** @internal */ export const hasInterrupts = (self: Cause.Cause): boolean => self.reasons.some(isInterruptReason) /** @internal */ export const findInterrupt = (self: Cause.Cause): Result.Result> => { const reason = self.reasons.find(isInterruptReason) return reason ? Result.succeed(reason) : Result.fail(self) } /** @internal */ export const causeFilterInterruptors = ( self: Cause.Cause ): Result.Result, Cause.Cause> => { let interruptors: Set | undefined for (let i = 0; i < self.reasons.length; i++) { const f = self.reasons[i] if (f._tag !== "Interrupt") continue interruptors ??= new Set() if (f.fiberId !== undefined) { interruptors.add(f.fiberId) } } return interruptors ? Result.succeed(interruptors) : Result.fail(self) } /** @internal */ export const causeInterruptors = (self: Cause.Cause): ReadonlySet => { const result = causeFilterInterruptors(self) return Result.isFailure(result) ? emptySet : result.success } const emptySet = new Set() /** @internal */ export const hasInterruptsOnly = (self: Cause.Cause): boolean => self.reasons.length > 0 && self.reasons.every(isInterruptReason) /** @internal */ export const reasonAnnotations = ( self: Cause.Reason ): Context.Context => Context.makeUnsafe(self.annotations) /** @internal */ export const causeAnnotations = ( self: Cause.Cause ): Context.Context => { const map = new Map() for (const f of self.reasons) { if (f.annotations.size > 0) { for (const [key, value] of f.annotations) { map.set(key, value) } } } return Context.makeUnsafe(map) } /** @internal */ export const causeMap: { (f: (error: NoInfer) => E2): (self: Cause.Cause) => Cause.Cause (self: Cause.Cause, f: (error: NoInfer) => E2): Cause.Cause } = dual( 2, (self: Cause.Cause, f: (error: NoInfer) => E2): Cause.Cause => { let hasFail = false const failures = self.reasons.map((failure) => { if (isFailReason(failure)) { hasFail = true return new Fail(f(failure.error), failure.annotations) } return failure }) return hasFail ? causeFromReasons(failures) : self as any } ) /** @internal */ export const causeSquash = (self: Cause.Cause): unknown => { let die: Cause.Die | undefined for (const reason of self.reasons) { if (reason._tag === "Fail") return reason.error if (reason._tag === "Die") die ??= reason } if (die !== undefined) return die.defect if (self.reasons.length > 0) return new globalThis.Error("All fibers interrupted without error") return new globalThis.Error("Empty cause") } /** @internal */ export const causePrettyErrors = (self: Cause.Cause, options?: { readonly includeCauseInStack?: boolean | undefined }): Array => { const errors: Array = [] const interrupts: Array = [] if (self.reasons.length === 0) return errors const prevStackLimit = getStackTraceLimit() if (prevStackLimit !== 0) setStackTraceLimit(1) try { for (const failure of self.reasons) { if (failure._tag === "Interrupt") { interrupts.push(failure) continue } errors.push( causePrettyError( failure._tag === "Die" ? failure.defect : failure.error as any, failure.annotations, options ) ) } if (errors.length === 0) { const cause = new Error("The fiber was interrupted by:") cause.name = "InterruptCause" cause.stack = interruptCauseStack(cause, interrupts) const error = new globalThis.Error("All fibers interrupted without error", { cause }) error.name = "InterruptError" error.stack = `${error.name}: ${error.message}` errors.push(causePrettyError(error, interrupts[0].annotations, options)) } } finally { if (prevStackLimit !== 0) setStackTraceLimit(prevStackLimit) } return errors } /** @internal */ export const causePrettyError = ( original: Record | Error, annotations?: ReadonlyMap, options?: { readonly includeCauseInStack?: boolean | undefined }, seen: Set = new Set() ): Error => { const kind = typeof original let error: Error if (original && kind === "object") { seen.add(original) const cause = original.cause error = new globalThis.Error(causePrettyMessage(original), { cause: cause !== undefined && !seen.has(cause) ? causePrettyError(cause as any, undefined, undefined, seen) : undefined }) if (typeof original.name === "string") { error.name = original.name } if (typeof original.stack === "string") { error.stack = cleanErrorStack(original.stack, error, annotations) } else { const stack = `${error.name}: ${error.message}` error.stack = annotations ? addStackAnnotations(stack, annotations) : stack } if (options?.includeCauseInStack) { error.stack = renderPrettyError(error)! } for (const key of Object.keys(original)) { if (!(key in error)) { ;(error as any)[key] = (original as any)[key] } } } else { error = new globalThis.Error( !original ? `Unknown error: ${original}` : kind === "string" ? original as any : formatJson(original) ) const stack = `${error.name}: ${error.message}` error.stack = annotations ? addStackAnnotations(stack, annotations) : stack } return error } const causePrettyMessage = (u: Record | Error): string => { if (typeof u.message === "string") { return u.message } else if ( typeof u.toString === "function" && u.toString !== Object.prototype.toString && u.toString !== Array.prototype.toString ) { try { return u.toString() } catch { // something's off, rollback to json } } return formatJson(u) } const locationRegExp = /\((.*)\)/g const cleanErrorStack = ( stack: string, error: Error, annotations: ReadonlyMap | undefined ): string => { const message = `${error.name}: ${error.message}` const lines = (stack.startsWith(message) ? stack.slice(message.length) : stack).split("\n") const out: Array = [message] for (let i = 1; i < lines.length; i++) { if (/Generator\.next|~effect\/(?:Effect|Utils)/.test(lines[i])) { break } out.push(lines[i]) } return annotations ? addStackAnnotations(out.join("\n"), annotations) : out.join("\n") } const addStackAnnotations = (stack: string, annotations: ReadonlyMap) => { const frame = annotations?.get(CauseStackTrace.key) as StackFrame | undefined if (frame) { stack = `${stack}\n${currentStackTrace(frame)}` } return stack } const interruptCauseStack = (error: Error, interrupts: Array): string => { const out: Array = [`${error.name}: ${error.message}`] for (const current of interrupts) { const fiberId = current.fiberId !== undefined ? `#${current.fiberId}` : "unknown" const frame = current.annotations.get(InterruptorStackTrace.key) as StackFrame | undefined out.push(` at fiber (${fiberId})`) if (frame) out.push(currentStackTrace(frame)) } return out.join("\n") } const currentStackTrace = (frame: StackFrame): string => { const out: Array = [] let current: StackFrame | undefined = frame let i = 0 while (current && i < 10) { const stack = current.stack() if (stack) { const locationMatchAll = stack.matchAll(locationRegExp) let match = false for (const [, location] of locationMatchAll) { match = true out.push(` at ${current.name} (${location})`) } if (!match) { out.push(` at ${current.name} (${stack.replace(/^at /, "")})`) } } else { out.push(` at ${current.name}`) } current = current.parent i++ } return out.join("\n") } /** @internal */ export const causePretty = (cause: Cause.Cause): string => causePrettyErrors(cause).map(renderPrettyError).join("\n") const renderPrettyError = (e: Error): string | undefined => e.cause ? `${e.stack} {\n${renderErrorCause(e.cause as Error, " ")}\n}` : e.stack const renderErrorCause = (cause: Error, prefix: string) => { const lines = cause.stack!.split("\n") let stack = `${prefix}[cause]: ${lines[0]}` for (let i = 1, len = lines.length; i < len; i++) { stack += `\n${prefix}${lines[i]}` } if (cause.cause) { stack += ` {\n${renderErrorCause(cause.cause as Error, `${prefix} `)}\n${prefix}}` } return stack } // ---------------------------------------------------------------------------- // Fiber // ---------------------------------------------------------------------------- /** @internal */ export const FiberTypeId = "~effect/Fiber" as const const fiberVariance = { _A: identity, _E: identity } const fiberIdStore = { id: 0 } interface AsyncContext { runInAsyncScope(fn: (this: This, arg: Arg) => R, thisArg: This, arg: Arg): R emitDestroy(): void } const AsyncResource: (new(type: string) => AsyncContext) | undefined = (() => { try { return (globalThis as any).process?.getBuiltinModule?.("node:async_hooks")?.AsyncResource } catch { return undefined } })() const captureAsyncContext = (): AsyncContext | undefined => AsyncResource === undefined ? undefined : new AsyncResource("effect/Fiber") /** @internal */ export const getCurrentFiber = (): Fiber.Fiber | undefined => (globalThis as any)[currentFiberTypeId] /** @internal */ export class FiberImpl implements Fiber.Fiber { constructor( context: Context.Context, interruptible: boolean = true ) { this.setContext(context) this.id = ++fiberIdStore.id this.currentOpCount = 0 this.interruptible = interruptible this._stack = [] this._observers = undefined this._exit = undefined this._children = undefined this._interruptedCause = undefined this._yielded = undefined this._running = false this._deferredInterrupt = false this._parent = undefined this._asyncContext = undefined this.cache.runtimeMetrics?.recordFiberStart(this.context) } get [FiberTypeId](): Fiber.Fiber.Variance { return fiberVariance } declare readonly id: number declare interruptible: boolean declare currentOpCount: number declare readonly _stack: Array declare _observers: Array<(exit: Exit.Exit) => void> | undefined declare _exit: Exit.Exit | undefined declare _children: Set> | undefined declare _interruptedCause: Cause.Cause | undefined declare _yielded: Exit.Exit | (() => void) | undefined declare _running: boolean declare _deferredInterrupt: boolean declare _parent: FiberImpl | undefined declare _asyncContext: AsyncContext | undefined // set in setContext declare context: Context.Context declare cache: Fiber.Fiber.Cache declare _dispatcher: Scheduler.SchedulerDispatcher | undefined get currentDispatcher(): Scheduler.SchedulerDispatcher { return this._dispatcher ??= this.cache.scheduler.makeDispatcher() } getRef(ref: Context.Reference): X { return Context.get(this.context, ref) } addObserver(cb: (exit: Exit.Exit) => void): () => void { if (this._exit) { cb(this._exit) return constVoid } if (this._observers === undefined) { this._observers = [cb] } else { this._observers.push(cb) } return () => this.removeObserver(cb) } removeObserver(cb: (exit: Exit.Exit) => void): void { if (this._exit || this._observers === undefined) return const index = this._observers.indexOf(cb) if (index >= 0) { this._observers.splice(index, 1) } } interruptUnsafe(fiberId?: number | undefined, annotations?: Context.Context | undefined): void { if (this._exit) { return } let cause = causeInterrupt(fiberId) if (this.cache.stackFrame) { cause = causeAnnotate(cause, Context.make(CauseStackTrace, this.cache.stackFrame)) } if (annotations) { cause = causeAnnotate(cause, annotations) } this._interruptedCause = this._interruptedCause ? causeAddInterrupts(this._interruptedCause, cause) : cause if (this.interruptible) { if (this._running) { this._deferredInterrupt = true } else { this.evaluate(failCause(this._interruptedCause) as any) } } } pollUnsafe(): Exit.Exit | undefined { return this._exit } evaluate(effect: Primitive): void { if (this._exit) { return } else if (this._asyncContext !== undefined) { const asyncContext = this._asyncContext this._asyncContext = undefined try { return asyncContext.runInAsyncScope(this.evaluate, this, effect) } finally { // Each captured context is resumed once, so release it for async_hooks // consumers like vitest's detectAsyncLeaks. asyncContext.emitDestroy() } } else if (this._yielded !== undefined) { const yielded = this._yielded as () => void this._yielded = undefined yielded() } const exit = this.runLoop(effect) if (exit === Yield) { this._asyncContext = captureAsyncContext() return } // the interruptChildren middleware is added in Effect.forkChild, so it can be // tree-shaken if not used const interruptChildren = fiberMiddleware.interruptChildren && fiberMiddleware.interruptChildren(this) if (interruptChildren !== undefined) { return this.evaluate(flatMap(interruptChildren, () => exit) as any) } this._exit = exit this.cache.runtimeMetrics?.recordFiberEnd(this.context, this._exit) if (this._parent) { this._parent._children?.delete(this) this._parent = undefined } if (this._observers !== undefined) { const observers = this._observers this._observers = undefined for (let i = 0; i < observers.length; i++) { observers[i](exit) } } this._stack.length = 0 this._children = undefined this.context = Context.empty() } runLoop(effect: Primitive): Exit.Exit | Yield { const prevFiber = (globalThis as any)[currentFiberTypeId] ;(globalThis as any)[currentFiberTypeId] = this const prevRunning = this._running this._running = true let yielding = false let current: Primitive | Yield = effect this.currentOpCount = 0 try { while (true) { try { if (this._deferredInterrupt) { this._deferredInterrupt = false current = failCause(this._interruptedCause!) as any } this.currentOpCount++ // Refresh the cache because a primitive can replace the fiber context. const cache = this.cache if ( !yielding && !cache.preventYield && cache.scheduler.shouldYield(this as any) ) { yielding = true const prev = current current = flatMap(yieldNow, () => prev as any) as any } current = cache.tracerContext ? cache.tracerContext(current as any, this) : (current as any)[evaluate](this) if (current === Yield) { const yielded = this._yielded! if (ExitTypeId in yielded) { this._deferredInterrupt = false this._yielded = undefined return yielded } else if (this._deferredInterrupt) { this._yielded = undefined yielded() continue } return Yield } } catch (error) { if (!hasProperty(current, evaluate)) { return exitDie(`Fiber.runLoop: Not a valid effect: ${String(current)}`) } current = exitDie(error) as any } } } finally { this._running = prevRunning ;(globalThis as any)[currentFiberTypeId] = prevFiber } } getCont(symbol: S): | (Primitive & Record Primitive>) | undefined { if (this._deferredInterrupt) { this._deferredInterrupt = false return deferredInterruptCont } while (true) { const op = this._stack.pop() if (!op) return undefined const all = op[contAll] if (all !== undefined) { const cont = all.call(op, this) if (cont) { ;(cont as any)[symbol] = cont return cont as any } } if (op[symbol]) return op as any } } // Passes a success value straight to the next continuation instead of // returning an Exit for the run loop to unwrap. Each call counts as an // operation, and every `maxInlineSteps`th call returns an Exit instead, so // continuations calling continuations never nest deeper than that. succeedWith(value: unknown): Primitive | Yield { if ((++this.currentOpCount & (maxInlineSteps - 1)) === 0) { return exitSucceed(value) as any } const cont = this.getCont(contA) return cont ? cont[contA](value, this) : this.yieldWith(exitSucceed(value)) } yieldWith(value: Exit.Exit | (() => void)): Yield { this._yielded = value return Yield } children(): Set> { return (this._children ??= new Set()) } pipe() { return pipeArguments(this, arguments) } setContext(context: Context.Context): void { const previous = this.context this.context = context // Every key cached below opts in to Context caching, so contexts related // only by non-caching adds cannot have changed any of them if (previous !== undefined && Context.hasSameCache(previous, context)) return // Contexts sharing a cacheRoot resolve every cached key identically, so // the derived cache object is computed once per root and shared by all // fibers running with that root (forked fibers reuse the parent's). const root: any = (context as any).cacheRoot const cache: Fiber.Fiber.Cache = root._fiberCache ??= makeFiberContextCache(context) if (this.cache?.scheduler !== cache.scheduler) { this._dispatcher = undefined } this.cache = cache } get currentSpanLocal(): Tracer.Span | undefined { const span = this.cache.span return span?._tag === "Span" ? span : undefined } } const makeFiberContextCache = (context: Context.Context): Fiber.Fiber.Cache => { // The string-keyed lookups keep the Tracer key values (and the native // tracer behind Tracer.Tracer's default) out of every bundle const currentTracer = Context.getOrUndefinedUnsafe(context, Tracer.TracerKey) return { scheduler: Context.get(context, Scheduler.Scheduler), tracer: currentTracer, tracerContext: currentTracer ? currentTracer["context"] : undefined, tracerEnabled: Context.get(context, TracerEnabled), span: Context.getOrUndefinedUnsafe(context, Tracer.ParentSpanKey), logLevel: Context.get(context, CurrentLogLevel), minimumLogLevel: Context.get(context, MinimumLogLevel), stackFrame: Context.get(context, CurrentStackFrame), runtimeMetrics: Context.getOrUndefinedUnsafe(context, InternalMetric.FiberRuntimeMetricsKey), maxOpsBeforeYield: Context.get(context, Scheduler.MaxOpsBeforeYield), preventYield: Context.get(context, Scheduler.PreventSchedulerYield) } } const deferredInterruptCont: any = { [contA](_value: unknown, fiber: FiberImpl) { return failCause(fiber._interruptedCause!) }, [contE](_cause: unknown, fiber: FiberImpl) { return failCause(fiber._interruptedCause!) } } // must be a power of two const maxInlineSteps = 32 const fiberMiddleware = { interruptChildren: undefined as | ((fiber: FiberImpl) => Effect.Effect | undefined) | undefined } const fiberStackAnnotations = (fiber: Fiber.Fiber) => { if (!fiber.cache.stackFrame) return undefined const annotations = new Map() annotations.set(InterruptorStackTrace.key, fiber.cache.stackFrame) return Context.makeUnsafe(annotations) } const fiberInterruptChildren = (fiber: FiberImpl) => { if (fiber._children === undefined || fiber._children.size === 0) { return undefined } return fiberInterruptAll(fiber._children) } /** @internal */ export const fiberAwait = ( self: Fiber.Fiber ): Effect.Effect> => { const impl = self as FiberImpl if (impl._exit) return succeed(impl._exit) return callback((resume) => { if (impl._exit) return resume(succeed(impl._exit)) return sync(self.addObserver((exit) => resume(succeed(exit)))) }) } /** @internal */ export const fiberAwaitAll = >( self: Iterable ): Effect.Effect< Array< Exit.Exit< Fiber extends Fiber.Fiber ? _A : never, Fiber extends Fiber.Fiber ? _E : never > > > => callback((resume) => { const iter = self[Symbol.iterator]() as Iterator const exits: Array> = [] let cancel: (() => void) | undefined = undefined function loop() { let result = iter.next() while (!result.done) { if (result.value._exit) { exits.push(result.value._exit) result = iter.next() continue } cancel = result.value.addObserver((exit) => { exits.push(exit) loop() }) return } resume(succeed(exits)) } loop() return sync(() => cancel?.()) }) /** @internal */ export const fiberJoin = (self: Fiber.Fiber): Effect.Effect => { const impl = self as FiberImpl if (impl._exit) return impl._exit return callback((resume) => { if (impl._exit) return resume(impl._exit) return sync(self.addObserver(resume)) }) } /** @internal */ export const fiberJoinAll = >>(self: A): Effect.Effect< Arr.ReadonlyArray.With> ? _A : never>, A extends Iterable> ? _E : never > => callback((resume) => { const fibers = Array.from(self) as Array if (fibers.length === 0) return resume(succeed(Arr.empty() as any)) const out = new Array(fibers.length) as Arr.NonEmptyArray const observers = Arr.empty<(exit: Exit.Exit) => void>() const removeObservers = () => { for (let i = 0; i < observers.length; i++) { fibers[i].removeObserver(observers[i]) } } let done = 0 let failed = false for (let i = 0; i < fibers.length; i++) { if (failed) break const observer = (exit: Exit.Exit) => { done++ if (exit._tag === "Failure") { failed = true removeObservers() return resume(exit as any) } out[i] = exit.value if (done === fibers.length) { resume(succeed(out)) } } observers.push(observer) fibers[i].addObserver(observer) } return sync(() => { failed = true removeObservers() }) }) /** @internal */ export const fiberInterrupt = ( self: Fiber.Fiber ): Effect.Effect => withFiber((fiber) => fiberInterruptAs(self, fiber.id)) /** @internal */ export const fiberInterruptAs: { ( fiberId: number | undefined, annotations?: Context.Context | undefined ): (self: Fiber.Fiber) => Effect.Effect ( self: Fiber.Fiber, fiberId: number | undefined, annotations?: Context.Context | undefined ): Effect.Effect } = dual( (args) => hasProperty(args[0], FiberTypeId), ( self: Fiber.Fiber, fiberId: number | undefined, annotations?: Context.Context | undefined ): Effect.Effect => withFiber((parent) => { let ann = fiberStackAnnotations(parent) ann = ann && annotations ? Context.merge(ann, annotations) : ann ?? annotations self.interruptUnsafe(fiberId, ann) return asVoid(fiberAwait(self)) }) ) /** @internal */ export const fiberInterruptAll = >>( fibers: A ): Effect.Effect => withFiber((parent) => { const annotations = fiberStackAnnotations(parent) let fiberArr = Arr.empty>() for (const fiber of fibers) { fiber.interruptUnsafe(parent.id, annotations) fiberArr.push(fiber) } return asVoid(fiberAwaitAll(fiberArr)) }) /** @internal */ export const fiberInterruptAllAs: { (fiberId: number): >>(fibers: A) => Effect.Effect >>(fibers: A, fiberId: number): Effect.Effect } = dual(2, >>( fibers: A, fiberId: number ): Effect.Effect => withFiber((parent) => { const annotations = fiberStackAnnotations(parent) const fiberArr = Arr.empty>() for (const fiber of fibers) { fiber.interruptUnsafe(fiberId, annotations) fiberArr.push(fiber) } return asVoid(fiberAwaitAll(fiberArr)) })) /** @internal */ export const succeed: (value: A) => Effect.Effect = exitSucceed /** @internal */ export const failCause: (cause: Cause.Cause) => Effect.Effect = exitFailCause /** @internal */ export const fail: (error: E) => Effect.Effect = exitFail /** @internal */ export const sync: (thunk: LazyArg) => Effect.Effect = makePrimitive({ op: "Sync", [evaluate](fiber): Primitive | Yield { const value = this[args]() const cont = fiber.getCont(contA) return cont ? cont[contA](value, fiber) : fiber.yieldWith(exitSucceed(value)) } }) /** @internal */ export const suspend: ( evaluate: LazyArg> ) => Effect.Effect = makePrimitive({ op: "Suspend", [evaluate](_fiber) { return this[args]() } }) /** @internal */ export const fromOption: < Arg extends Option.Option | LazyArg, Rest extends [] | [onNone: LazyArg | undefined] = [] >( arg: Arg & (Rest extends [] ? unknown : Option.Option), ...rest: Rest ) => [Arg] extends [Option.Option] ? Effect.Effect< A, Rest extends [LazyArg] ? E : Rest extends [undefined] ? Cause.NoSuchElementError : Rest extends [LazyArg | undefined] ? E | Cause.NoSuchElementError : Cause.NoSuchElementError > : [Arg] extends [LazyArg] ? (option: Option.Option) => Effect.Effect : never = dual( (args) => args.length >= 2 || Option.isOption(args[0]), (option: Option.Option, onNone?: LazyArg): Effect.Effect => Option.isNone(option) ? onNone ? failSync(onNone) : fail(new NoSuchElementError("Effect.fromOption: Option.none")) : succeed(option.value) ) /** @internal */ export const fromResult: (result: Result.Result) => Effect.Effect = Result.match({ onFailure: fail, onSuccess: succeed }) /** @internal */ export const fromNullishOr = (value: A): Effect.Effect, Cause.NoSuchElementError> => value == null ? fail(new NoSuchElementError()) : succeed(value) /** @internal */ export const yieldNowWith: (priority?: number) => Effect.Effect = makePrimitive({ op: "Yield", [evaluate](fiber) { let resumed = false fiber.currentDispatcher.scheduleTask(() => { if (resumed) return fiber.evaluate(exitVoid as any) }, this[args] ?? 0) return fiber.yieldWith(() => { resumed = true }) } }) /** @internal */ export const yieldNow: Effect.Effect = yieldNowWith(0) /** @internal */ export const succeedSome = (a: A): Effect.Effect> => succeed(Option.some(a)) /** @internal */ export const succeedNone: Effect.Effect> = succeed( Option.none() ) /** @internal */ export const transposeOption = ( self: Option.Option> ): Effect.Effect, E, R> => Option.isNone(self) ? succeedNone : map(self.value, Option.some) /** @internal */ export const failCauseSync = ( evaluate: LazyArg> ): Effect.Effect => suspend(() => failCause(evaluate())) /** @internal */ export const die = (defect: unknown): Effect.Effect => exitDie(defect) /** @internal */ export const failSync = (error: LazyArg): Effect.Effect => suspend(() => fail(error())) /** @internal */ const void_: Effect.Effect = succeed(void 0) /** @internal */ export { void_ as void } /** @internal */ const try_ = ( options: { readonly try: LazyArg readonly catch: (error: unknown) => E } | LazyArg ): Effect.Effect => { const evaluate = typeof options === "function" ? options : options.try const catcher = typeof options === "function" ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.try")) : options.catch return suspend(() => { try { return succeed(evaluate()) } catch (err) { return fail(catcher(err) as E) } }) } /** @internal */ export { try_ as try } /** @internal */ export const promise = ( evaluate: (signal: AbortSignal) => PromiseLike ): Effect.Effect => callbackOptions(function(resume, signal) { evaluate(signal!).then( (a) => resume(succeed(a)), (e) => resume(die(e)) ) }, evaluate.length !== 0) /** @internal */ export const tryPromise = ( options: { readonly try: (signal: AbortSignal) => PromiseLike readonly catch: (error: unknown) => E } | ((signal: AbortSignal) => PromiseLike) ): Effect.Effect => { const f = typeof options === "function" ? options : options.try const catcher = typeof options === "function" ? ((cause: unknown) => new UnknownError(cause, "An error occurred in Effect.tryPromise")) : options.catch return callbackOptions(function(resume, signal) { // Defer the catcher until the fiber restores its async context. const failWithCatch = (cause: unknown) => suspend(() => { try { return fail(internalCall(() => catcher(cause)) as E) } catch (err) { return die(err) } }) try { f(signal!).then( (a) => resume(succeed(a)), (e) => resume(failWithCatch(e)) ) } catch (err) { resume(failWithCatch(err)) } }, f.length !== 0) } /** @internal */ export const withFiberId = ( f: (fiberId: number) => Effect.Effect ): Effect.Effect => withFiber((fiber) => f(fiber.id)) /** @internal */ export const fiber = withFiber(succeed) /** @internal */ export const fiberId = withFiberId(succeed) const callbackOptions: ( register: ( this: Scheduler.Scheduler, resume: (effect: Effect.Effect) => void, signal?: AbortSignal ) => void | Effect.Effect, withSignal: boolean ) => Effect.Effect = (function() { const Proto = makePrimitiveProto({ op: "Async", [evaluate](this: any, fiber) { let resumed = false let yielded: boolean | Primitive = false const controller = this.withSignal ? new AbortController() : undefined const onCancel = this.register.call(fiber.cache.scheduler, (effect: Effect.Effect) => { if (resumed) return resumed = true if (yielded) { fiber.evaluate(effect as any) } else { yielded = effect as any } }, controller?.signal) if (yielded !== false) return yielded yielded = true // V8 pretenures closures assigned to a property; a method argument avoids that and survives minification fiber.yieldWith(() => { resumed = true }) if (controller === undefined && onCancel === undefined) { return Yield } fiber._stack.push( asyncFinalizer(() => { resumed = true controller?.abort() return onCancel ?? exitVoid }) ) return Yield } }) const AsyncImpl = function(this: any, register: any, withSignal: boolean) { this.register = register this.withSignal = withSignal } as unknown as PrimitiveCtor<[register: any, withSignal: boolean]> AsyncImpl.prototype = Proto return function(register: any, withSignal: boolean) { return new AsyncImpl(register, withSignal) } as any })() const asyncFinalizer: ( onInterrupt: () => Effect.Effect ) => Primitive = makePrimitive({ op: "AsyncFinalizer", [contAll](fiber) { if (fiber.interruptible) { fiber.interruptible = false fiber._stack.push(setInterruptibleTrue) } }, [contE](cause, _fiber) { return hasInterrupts(cause) ? flatMap(combineFinalizerCause(exitFailCause(cause), this[args]()), () => failCause(cause)) : failCause(cause) } }) /** @internal */ export const callback = ( register: ( this: Scheduler.Scheduler, resume: (effect: Effect.Effect) => void, signal: AbortSignal ) => void | Effect.Effect ): Effect.Effect => callbackOptions(register as any, register.length >= 2) /** @internal */ export const never: Effect.Effect = callback(constVoid) /** @internal */ export const gen = < Self, Eff extends Effect.Effect, AEff >( ...args: | [options: { readonly self: Self }, body: (this: Self) => Generator] | [body: () => Generator] ): Effect.Effect< AEff, [Eff] extends [never] ? never : [Eff] extends [Effect.Effect] ? E : never, [Eff] extends [never] ? never : [Eff] extends [Effect.Effect] ? R : never > => { if (args.length === 1) { const body = args[0] return suspend(() => fromIteratorUnsafe(body())) } const [options, body] = args return suspend(() => fromIteratorUnsafe(body.call(options.self) as any)) } /** @internal */ export const fnUntraced: Effect.fn.Untraced = ( body: Function, ...pipeables: Array ) => { const fn = pipeables.length === 0 ? function(this: any) { return suspend(() => fromIteratorUnsafe(body.apply(this, arguments))) } : function(this: any) { let effect = suspend(() => fromIteratorUnsafe(body.apply(this, arguments))) for (let i = 0; i < pipeables.length; i++) { effect = pipeables[i](effect, ...arguments) } return effect } return defineFunctionLength(body.length, fn) } const defineFunctionLength = (length: number, fn: F): F => Object.defineProperty(fn, "length", { value: length, configurable: true }) const fnStackCleaner = makeStackCleaner(2) /** @internal */ export const fn: typeof Effect.fn = function() { const nameFirst = typeof arguments[0] === "string" const name = nameFirst ? arguments[0] : "Effect.fn" const spanOptions = nameFirst ? arguments[1] : undefined const prevLimit = getStackTraceLimit() let defError: Error | undefined if (prevLimit !== 0) { setStackTraceLimit(2) defError = new globalThis.Error() setStackTraceLimit(prevLimit) } if (nameFirst) { return (body: Function | { readonly self: any }, ...pipeables: Array) => makeFn(name, body, defError, pipeables, nameFirst, spanOptions) } return makeFn( name, arguments[0], defError, Array.prototype.slice.call(arguments, 1), nameFirst, spanOptions ) } as any const makeFn = ( name: string, bodyOrOptions: Function | { readonly self: any }, defError: Error | undefined, pipeables: Array, addSpan: boolean, spanOptions: Tracer.SpanOptionsNoTrace | undefined ) => { const body = typeof bodyOrOptions === "function" ? bodyOrOptions : (pipeables.shift()!).bind(bodyOrOptions.self) const definitionName = `${name} (definition)` const definitionStack = defError ? fnStackCleaner(() => defError.stack) : constUndefined return defineFunctionLength(body.length, function(this: any, ...args: Array) { let result = suspend(() => { const iter = body.apply(this, arguments) return isEffect(iter) ? iter : fromIteratorUnsafe(iter) }) for (let i = 0; i < pipeables.length; i++) { result = pipeables[i](result, ...args) } if (!isEffect(result)) { return result } const prevLimit = getStackTraceLimit() let callError: Error | undefined if (prevLimit !== 0) { setStackTraceLimit(2) callError = new globalThis.Error() setStackTraceLimit(prevLimit) } return updateService( addSpan ? useSpan(name, spanOptions!, (span) => provideParentSpan(result, span)) : result, CurrentStackFrame, (prev) => ({ name, stack: callError ? fnStackCleaner(() => callError.stack) : constUndefined, parent: { name: definitionName, stack: definitionStack, parent: prev } }) ) }) } /** @internal */ export const fnUntracedEager: Effect.fn.Untraced = ( body: Function, ...pipeables: Array ) => defineFunctionLength( body.length, pipeables.length === 0 ? function(this: any) { return fromIteratorEagerUnsafe(() => body.apply(this, arguments)) } : function(this: any) { let effect = fromIteratorEagerUnsafe(() => body.apply(this, arguments)) for (const pipeable of pipeables) { effect = pipeable(effect, ...arguments) } return effect } ) const fromIteratorEagerUnsafe = ( evaluate: () => Iterator> ): Effect.Effect => { try { const iterator = evaluate() let value: any = undefined // Try to resolve synchronously in a loop while (true) { const state = iterator.next(value) if (state.done) { return succeed(state.value) } const primitive = state.value as any if (primitive && primitive._tag === "Success") { value = primitive.value continue } else if (primitive && primitive._tag === "Failure") { return state.value } else { let isFirstExecution = true return suspend(() => { if (isFirstExecution) { isFirstExecution = false return flatMap(state.value, (value) => fromIteratorUnsafe(iterator, value)) } else { return suspend(() => fromIteratorUnsafe(evaluate())) } }) } } } catch (error) { return die(error) } } const fromIteratorUnsafe: ( iterator: Iterator>, initial?: undefined ) => Effect.Effect = (function() { const Proto = makePrimitiveProto({ op: "Iterator", [contA](this: any, value, fiber) { const iter = this.iterator while (true) { const state = iter.next(value) if (state.done) return succeed(state.value) if (!effectIsExit(state.value)) { fiber._stack.push(this) return state.value } else if (state.value._tag === "Failure") { return state.value } value = state.value.value } }, [evaluate](this: any, fiber: FiberImpl) { return this[contA](this.initial, fiber) } }) const IteratorImpl = function(this: any, iterator: any, initial: any) { this.iterator = iterator this.initial = initial } as unknown as PrimitiveCtor<[iterator: any, initial: any]> IteratorImpl.prototype = Proto return function(iterator: any, initial?: undefined) { return new IteratorImpl(iterator, initial) } as any })() // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** @internal */ export const as: { ( value: B ): (self: Effect.Effect) => Effect.Effect (self: Effect.Effect, value: B): Effect.Effect } = dual( 2, ( self: Effect.Effect, value: B ): Effect.Effect => new ContImpl(self, returnPayload, succeed(value)) ) const evaluateCont = function(this: any, fiber: FiberImpl): Primitive { fiber._stack.push(this) return this[args] } interface PrimitiveCtor> { new(...args: Args): Primitive & Effect.Effect prototype: any } const OnSuccessProto = makePrimitiveProto({ op: "OnSuccess", [evaluate]: evaluateCont }) // A success continuation with an extra payload slot. The stored continuation // receives the primitive as `this` and reads `this.payload`, so combinators // like map / as / tap / andThen / flatMap can share module-level continuation // functions instead of allocating a closure per call. const ContImpl = function( this: any, self: Effect.Effect, cont: (this: { readonly payload: any }, value: any, fiber: FiberImpl) => unknown, payload: unknown ) { this[args] = self this[contA] = cont this.payload = payload } as unknown as PrimitiveCtor< [ self: Effect.Effect, cont: (this: { readonly payload: any }, value: any, fiber: FiberImpl) => unknown, payload: unknown ] > ContImpl.prototype = OnSuccessProto const returnPayload = function(this: { readonly payload: any }) { return this.payload } const succeedPayload = function(this: { readonly payload: any }, _value: unknown, fiber: FiberImpl) { return fiber.succeedWith(this.payload) } // V8 includes the property name of a stored continuation in its stack trace. // Other engines need an explicit frame for the stack cleaner to cut at. const continuationMarksStack = (() => { const limit = getStackTraceLimit() if (limit === 0) return false const marker = "~effect/Effect/stackProbe" const probe = { [marker]: function stackProbe() { return new Error() } } // Only the probe frame is needed to detect the continuation property name. setStackTraceLimit(1) const error = probe[marker]() setStackTraceLimit(limit) return error.stack?.includes("[as " + marker + "]") === true })() const mapCont = function(this: { readonly payload: any }, value: any, fiber: FiberImpl) { const f = this.payload return fiber.succeedWith(continuationMarksStack ? f(value) : internalCall(() => f(value))) } const andThenCont = function(this: { readonly payload: any }, value: any) { const f = this.payload return f(value) } const tapCont = function(this: { readonly payload: any }, value: any) { const f = this.payload return new ContImpl(f(value), succeedPayload, value) } const tapEffectCont = function(this: { readonly payload: any }, value: any) { return new ContImpl(this.payload, succeedPayload, value) } /** @internal */ export const asSome = ( self: Effect.Effect ): Effect.Effect, E, R> => map(self, Option.some) /** @internal */ export const flip = ( self: Effect.Effect ): Effect.Effect => matchEffect(self, { onFailure: succeed, onSuccess: fail }) /** @internal */ export const andThen: { ( f: (a: A) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( f: Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: A) => Effect.Effect ): Effect.Effect ( self: Effect.Effect, f: Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: ((a: A) => Effect.Effect) | Effect.Effect ): Effect.Effect => new ContImpl(self, isEffect(f) ? returnPayload : andThenCont, f) ) /** @internal */ export const tap: { ( f: (a: NoInfer) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( f: Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: NoInfer) => Effect.Effect ): Effect.Effect ( self: Effect.Effect, f: Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: ((a: A) => Effect.Effect) | Effect.Effect ): Effect.Effect => new ContImpl(self, isEffect(f) ? tapEffectCont : tapCont, f) ) /** @internal */ export const asVoid = ( self: Effect.Effect ): Effect.Effect => new ContImpl(self, returnPayload, exitVoid) /** @internal */ export const sandbox = ( self: Effect.Effect ): Effect.Effect, R> => catchCause(self, fail) /** @internal */ export const raceAll = >( all: Iterable, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect< Effect.Success, Effect.Error, Effect.Services > => withFiber((parent) => { const fibers = new Set>() // Read fibers on exit to include losers forked after the race settles. onExitUnsafe(parent, () => fibers.size === 0 ? undefined : fiberInterruptAll(fibers)) return callback((resume) => { const effects = Arr.fromIterable(all) const len = effects.length let doneCount = 0 let done = false const failures: Array> = [] const onExit = (exit: Exit.Exit, fiber: Fiber.Fiber, i: number) => { doneCount++ if (exit._tag === "Failure") { failures.push(...exit.cause.reasons) if (doneCount >= len) { resume(failCause(causeElectDefects(causeFromReasons(failures)))) } return } const isWinner = !done done = true resume(exit) if (isWinner && options?.onWinner) { options.onWinner({ fiber, index: i, parentFiber: parent }) } } for (let i = 0; i < len; i++) { const fiber = forkUnsafe(parent, effects[i], true, true, false) fibers.add(fiber) fiber.addObserver((exit) => { fibers.delete(fiber) onExit(exit, fiber, i) }) if (done) break } }) }) /** @internal */ export const raceAllFirst = >( all: Iterable, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect< Effect.Success, Effect.Error, Effect.Services > => withFiber((parent) => { const fibers = new Set>() onExitUnsafe(parent, () => fibers.size === 0 ? undefined : fiberInterruptAll(fibers)) return callback((resume) => { let done = false let i = 0 for (const effect of all) { if (done) break const index = i++ const fiber = forkUnsafe(parent, effect, true, true, false) fibers.add(fiber) fiber.addObserver((exit) => { fibers.delete(fiber) const isWinner = !done done = true resume(exit) if (isWinner && options?.onWinner) { options.onWinner({ fiber, index, parentFiber: parent }) } }) } }) }) /** @internal */ export const race: { ( that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect } = dual( (args) => isEffect(args[1]), ( self: Effect.Effect, that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect => raceAll([self, that], options) ) /** @internal */ export const raceFirst: { ( that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect } = dual( (args) => isEffect(args[1]), ( self: Effect.Effect, that: Effect.Effect, options?: { readonly onWinner?: (options: { readonly fiber: Fiber.Fiber readonly index: number readonly parentFiber: Fiber.Fiber }) => void } ): Effect.Effect => raceAllFirst([self, that], options) ) /** @internal */ export const flatMap: { ( f: (a: A) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: A) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (a: A) => Effect.Effect ): Effect.Effect => new ContImpl(self, andThenCont, f) ) /** @internal */ export const matchCauseEffectEager: { (options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect }): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect => { if (effectIsExit(self)) { return self._tag === "Success" ? options.onSuccess(self.value) : options.onFailure(self.cause) } return matchCauseEffect(self, options) } ) /** @internal */ export const effectIsExit = (effect: Effect.Effect): effect is Exit.Exit => (effect as any)[ExitTypeId] !== undefined /** @internal */ export const flatMapEager: { ( f: (a: A) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: A) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (a: A) => Effect.Effect ): Effect.Effect => { if (effectIsExit(self)) { return self._tag === "Success" ? f(self.value) : self as Exit.Exit } return flatMap(self, f) } ) // ---------------------------------------------------------------------------- // mapping & sequencing // ---------------------------------------------------------------------------- /** @internal */ export const flatten = ( self: Effect.Effect, E2, R2> ): Effect.Effect => flatMap(self, identity) /** @internal */ export const map: { ( f: (a: A) => B ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: A) => B ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (a: A) => B ): Effect.Effect => new ContImpl(self, mapCont, f) ) /** @internal */ export const mapEager: { ( f: (a: A) => B ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (a: A) => B ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (a: A) => B ): Effect.Effect => effectIsExit(self) ? exitMap(self, f) : map(self, f) ) /** @internal */ export const mapErrorEager: { ( f: (e: E) => E2 ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (e: E) => E2 ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (e: E) => E2 ): Effect.Effect => effectIsExit(self) ? exitMapError(self, f) : mapError(self, f) ) /** @internal */ export const mapBothEager: { ( options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Effect.Effect => effectIsExit(self) ? exitMapBoth(self, options) : mapBoth(self, options) ) /** @internal */ export const catchEager: { ( f: (e: NoInfer) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (e: NoInfer) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (e: NoInfer) => Effect.Effect ): Effect.Effect => { if (effectIsExit(self)) { if (self._tag === "Success") return self as Exit.Exit const error = findRecoverableError(self.cause) if (Result.isFailure(error)) return exitFailCause(error.failure) return f(error.success) } return catch_(self, f) } ) // ---------------------------------------------------------------------------- // Exit // ---------------------------------------------------------------------------- /** @internal */ export const exitInterrupt = (fiberId?: number | undefined): Exit.Exit => exitFailCause(causeInterrupt(fiberId)) /** @internal */ export const exitIsSuccess = ( self: Exit.Exit ): self is Exit.Success => self._tag === "Success" /** @internal */ export const exitFilterSuccess = ( self: Exit.Exit ): Result.Result, Exit.Failure> => self._tag === "Success" ? Result.succeed(self as any) : Result.fail(self as any) /** @internal */ export const exitFilterValue = ( self: Exit.Exit ): Result.Result> => self._tag === "Success" ? Result.succeed(self.value) : Result.fail(self as any) /** @internal */ export const exitIsFailure = ( self: Exit.Exit ): self is Exit.Failure => self._tag === "Failure" /** @internal */ export const exitFilterFailure = ( self: Exit.Exit ): Result.Result, Exit.Success> => self._tag === "Failure" ? Result.succeed(self as any) : Result.fail(self as any) /** @internal */ export const exitFilterCause = ( self: Exit.Exit ): Result.Result, Exit.Success> => self._tag === "Failure" ? Result.succeed(self.cause) : Result.fail(self as any) /** @internal */ export const exitFindError = Filter.composePassthrough( exitFilterCause, findError ) /** @internal */ export const exitFindDefect = Filter.composePassthrough( exitFilterCause, findDefect ) /** @internal */ export const exitHasInterrupts = ( self: Exit.Exit ): self is Exit.Failure => self._tag === "Failure" && hasInterrupts(self.cause) /** @internal */ export const exitHasDies = ( self: Exit.Exit ): self is Exit.Failure => self._tag === "Failure" && hasDies(self.cause) /** @internal */ export const exitHasFails = ( self: Exit.Exit ): self is Exit.Failure => self._tag === "Failure" && hasFails(self.cause) /** @internal */ export const exitVoid: Exit.Exit = exitSucceed(void 0) /** @internal */ export const exitMap: { (f: (a: A) => B): (self: Exit.Exit) => Exit.Exit (self: Exit.Exit, f: (a: A) => B): Exit.Exit } = dual( 2, (self: Exit.Exit, f: (a: A) => B): Exit.Exit => self._tag === "Success" ? exitSucceed(f(self.value)) : (self as any) ) /** @internal */ export const exitMapError: { (f: (a: NoInfer) => E2): (self: Exit.Exit) => Exit.Exit (self: Exit.Exit, f: (a: NoInfer) => E2): Exit.Exit } = dual( 2, (self: Exit.Exit, f: (a: NoInfer) => E2): Exit.Exit => { if (self._tag === "Success") return self as Exit.Exit return exitFailCause(causeMap(self.cause, f)) } ) /** @internal */ export const exitMapBoth: { ( options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): (self: Exit.Exit) => Exit.Exit ( self: Exit.Exit, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Exit.Exit } = dual( 2, ( self: Exit.Exit, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Exit.Exit => { if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) return exitFailCause(causeMap(self.cause, options.onFailure)) } ) /** @internal */ export const exitAs: { (b: B): (self: Exit.Exit) => Exit.Exit (self: Exit.Exit, b: B): Exit.Exit } = dual( 2, (self: Exit.Exit, b: B): Exit.Exit => exitIsSuccess(self) ? exitSucceed(b) : (self as any) ) /** @internal */ export const exitZipRight: { ( that: Exit.Exit ): (self: Exit.Exit) => Exit.Exit ( self: Exit.Exit, that: Exit.Exit ): Exit.Exit } = dual( 2, ( self: Exit.Exit, that: Exit.Exit ): Exit.Exit => (exitIsSuccess(self) ? that : (self as any)) ) /** @internal */ export const exitMatch: { (options: { readonly onSuccess: (a: NoInfer) => X1 readonly onFailure: (cause: Cause.Cause>) => X2 }): (self: Exit.Exit) => X1 | X2 ( self: Exit.Exit, options: { readonly onSuccess: (a: A) => X1 readonly onFailure: (cause: Cause.Cause) => X2 } ): X1 | X2 } = dual( 2, ( self: Exit.Exit, options: { readonly onSuccess: (a: A) => X1 readonly onFailure: (cause: Cause.Cause) => X2 } ): X1 | X2 => exitIsSuccess(self) ? options.onSuccess(self.value) : options.onFailure(self.cause) ) /** @internal */ export const exitAsVoid: (self: Exit.Exit) => Exit.Exit = exitAs(void 0) /** @internal */ export const exitAsVoidAll = >>( exits: I ): Exit.Exit< void, I extends Iterable> ? _E : never > => { const failures: Array> = [] for (const exit of exits) { if (exit._tag === "Failure") { failures.push(...exit.cause.reasons) } } return failures.length === 0 ? exitVoid : exitFailCause(causeFromReasons(failures)) } /** @internal */ export const exitGetSuccess = (self: Exit.Exit): Option.Option => exitIsSuccess(self) ? Option.some(self.value) : Option.none() /** @internal */ export const exitGetCause = (self: Exit.Exit): Option.Option> => exitIsFailure(self) ? Option.some(self.cause) : Option.none() /** @internal */ export const exitFindErrorOption = (self: Exit.Exit): Option.Option => { const error = exitFindError(self) return Result.isFailure(error) ? Option.none() : Option.some(error.success) } // ---------------------------------------------------------------------------- // environment // ---------------------------------------------------------------------------- /** @internal */ export const service = (service: Context.Key): Effect.Effect => service /** @internal */ export const serviceOption = ( service: Context.Key ): Effect.Effect> => withFiber((fiber) => succeed(Context.getOption(fiber.context, service))) /** @internal */ export const serviceOptional = ( service: Context.Key ): Effect.Effect => withFiber((fiber) => fromOption(Context.getOption(fiber.context, service))) /** @internal */ export const updateContext: { ( f: (context: Context.Context) => Context.Context> ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (context: Context.Context) => Context.Context> ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (context: Context.Context) => Context.Context> ): Effect.Effect => withFiber((fiber) => { const prevContext = fiber.context as Context.Context const nextContext = f(prevContext) if (prevContext === nextContext) return self as any fiber.setContext(nextContext) onExitUnsafe(fiber, () => { fiber.setContext(prevContext) return undefined }) return self as any }) ) /** @internal */ export const updateService: { ( service: Context.Key, f: (value: A) => A ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, service: Context.Key, f: (value: A) => A ): Effect.Effect } = dual( 3, ( self: Effect.Effect, service: Context.Key, f: (value: A) => A ): Effect.Effect => updateContext(self, (s) => { const prev = Context.getUnsafe(s, service) const next = f(prev) if (prev === next) return s return Context.add(s, service, next) }) ) /** @internal */ export const updateServiceScoped = ( service: Context.Key, update: (value: A) => A, options?: { readonly reset?: ((original: A, updated: A, current: A) => A) | undefined } | undefined ): Effect.Effect => uninterruptible(withFiber((fiber) => { const original = Context.getUnsafe(fiber.context, service) const updated = update(original) fiber.setContext(Context.add(fiber.context, service, updated)) return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => { const currentOption = Context.getOption(fiber.context, service) if (Option.isNone(currentOption)) return void_ const current = currentOption.value let next: A if (options?.reset === undefined) { if (current !== updated) return void_ next = original } else { next = options.reset(original, updated, current) } fiber.setContext(Context.add(fiber.context, service, next)) return void_ }) })) /** @internal */ export const context = (): Effect.Effect> => getContext as any const getContext = withFiber((fiber) => succeed(fiber.context)) /** @internal */ export const contextWith = ( f: (context: Context.Context) => Effect.Effect ): Effect.Effect => withFiber((fiber) => f(fiber.context as Context.Context)) /** @internal */ export const setContext: { ( context: Context.Context ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, context: Context.Context ): Effect.Effect } = dual( 2, ( self: Effect.Effect, context: Context.Context ): Effect.Effect => updateContext(self, constant(context)) ) /** @internal */ export const provideContext: { ( context: Context.Context ): ( self: Effect.Effect ) => Effect.Effect> ( self: Effect.Effect, context: Context.Context ): Effect.Effect> } = dual( 2, ( self: Effect.Effect, context: Context.Context ): Effect.Effect> => { if (effectIsExit(self)) return self as any return updateContext(self, Context.merge(context)) as any } ) /** @internal */ export const provideService: { ( service: Context.Key ): { (implementation: S): (self: Effect.Effect) => Effect.Effect> (self: Effect.Effect, implementation: S): Effect.Effect> } ( key: Context.Key, implementation: S ): ( self: Effect.Effect ) => Effect.Effect> ( self: Effect.Effect, service: Context.Key, implementation: S ): Effect.Effect> } = function(this: any) { if (arguments.length === 1) { return dual(2, (self, impl) => provideServiceImpl(self, arguments[0], impl)) as any } return dual(3, (self, service, impl) => provideServiceImpl(self, service, impl)) .apply(this, arguments as any) as any } const provideServiceImpl = ( self: Effect.Effect, service: Context.Key, implementation: S ): Effect.Effect> => updateContext(self, Context.add(service, implementation)) as any /** @internal */ export const provideServiceEffect: { ( service: Context.Key, acquire: Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect | R2> ( self: Effect.Effect, service: Context.Key, acquire: Effect.Effect ): Effect.Effect | R2> } = dual( 3, ( self: Effect.Effect, service: Context.Key, acquire: Effect.Effect ): Effect.Effect | R2> => flatMap(acquire, (implementation) => provideService(self, service, implementation)) ) // ---------------------------------------------------------------------------- // zipping // ---------------------------------------------------------------------------- /** @internal */ export const zip: { ( that: Effect.Effect, options?: { readonly concurrent?: boolean | undefined } | undefined ): ( self: Effect.Effect ) => Effect.Effect<[A, A2], E2 | E, R2 | R> ( self: Effect.Effect, that: Effect.Effect, options?: { readonly concurrent?: boolean | undefined } ): Effect.Effect<[A, A2], E | E2, R | R2> } = dual( (args) => isEffect(args[1]), ( self: Effect.Effect, that: Effect.Effect, options?: { readonly concurrent?: boolean | undefined } ): Effect.Effect<[A, A2], E | E2, R | R2> => zipWith(self, that, (a, a2) => [a, a2], options) ) /** @internal */ export const zipWith: { ( that: Effect.Effect, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, that: Effect.Effect, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): Effect.Effect } = dual( (args) => isEffect(args[1]), ( self: Effect.Effect, that: Effect.Effect, f: (a: A, b: A2) => B, options?: { readonly concurrent?: boolean | undefined } ): Effect.Effect => options?.concurrent // Use `all` exclusively for concurrent cases, as it introduces additional overhead due to the management of concurrency ? map(all([self, that], { concurrency: 2 }), ([a, a2]) => f(a, a2)) : flatMap(self, (a) => map(that, (a2) => f(a, a2))) ) // ---------------------------------------------------------------------------- // filtering & conditionals // ---------------------------------------------------------------------------- /** @internal */ export const filterOrFail: { ( refinement: Predicate.Refinement, B>, orFailWith: (a: NoInfer) => E2 ): (self: Effect.Effect) => Effect.Effect ( predicate: Predicate.Predicate>, orFailWith: (a: NoInfer) => E2 ): (self: Effect.Effect) => Effect.Effect ( refinement: Predicate.Refinement, B> ): (self: Effect.Effect) => Effect.Effect ( predicate: Predicate.Predicate> ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, refinement: Predicate.Refinement, B>, orFailWith: (a: NoInfer) => E2 ): Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate>, orFailWith: (a: NoInfer) => E2 ): Effect.Effect ( self: Effect.Effect, refinement: Predicate.Refinement, B> ): Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate> ): Effect.Effect } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, predicate: Predicate.Predicate>, orFailWith?: (a: any) => E2 ): Effect.Effect => filterOrElse( self, predicate as any, orFailWith ? (a: any) => fail(orFailWith(a)) : () => fail(new NoSuchElementError() as E2) )) /** @internal */ export const when: { ( condition: Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect, E | E2, R | R2> ( self: Effect.Effect, condition: Effect.Effect ): Effect.Effect, E | E2, R | R2> } = dual( 2, ( self: Effect.Effect, condition: Effect.Effect ): Effect.Effect, E | E2, R | R2> => flatMap(condition, (pass) => pass ? asSome(self) : succeedNone) ) // ---------------------------------------------------------------------------- // repetition // ---------------------------------------------------------------------------- /** @internal */ export const replicate: { ( n: number ): (self: Effect.Effect) => Array> ( self: Effect.Effect, n: number ): Array> } = dual( 2, ( self: Effect.Effect, n: number ): Array> => Array.from({ length: n }, () => self) ) /** @internal */ export const replicateEffect: { ( n: number, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } ): (self: Effect.Effect) => Effect.Effect, E, R> ( n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, n: number, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } ): Effect.Effect, E, R> ( self: Effect.Effect, n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Effect.Effect } = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, n: number, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Effect.Effect => all(replicate(self, n), options) ) /** @internal */ export const forever: { < Arg extends Effect.Effect | { readonly disableYield?: boolean | undefined } | undefined = { readonly disableYield?: boolean | undefined } >( effectOrOptions: Arg, options?: { readonly disableYield?: boolean | undefined } | undefined ): [Arg] extends [Effect.Effect] ? Effect.Effect : (self: Effect.Effect) => Effect.Effect } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly disableYield?: boolean | undefined } ): Effect.Effect => whileLoop({ while: constTrue, body: constant(options?.disableYield ? self : flatMap(self, (_) => yieldNow)), step: constVoid }) as any) // ---------------------------------------------------------------------------- // error handling // ---------------------------------------------------------------------------- /** @internal */ export const catchCause: { ( f: (cause: NoInfer>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (cause: NoInfer>) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (cause: NoInfer>) => Effect.Effect ): Effect.Effect => new OnFailureImpl(self, f.length !== 1 ? (cause: Cause.Cause) => f(cause) : f) ) const OnFailureProto = makePrimitiveProto({ op: "OnFailure", [evaluate]: evaluateCont }) const OnFailureImpl = function(this: any, self: Effect.Effect, f: any) { this[args] = self this[contE] = f } as unknown as PrimitiveCtor<[self: Effect.Effect, f: any]> OnFailureImpl.prototype = OnFailureProto /** @internal */ export const catchCauseIf: { ( predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect => catchCause(self, (cause): Effect.Effect => { if (!predicate(cause)) { return failCause(cause) as any } return f(cause) }) ) /** @internal */ export const catchCauseFilter: { >( filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect | E2, R | R2> >( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect | E2, R | R2> } = dual( 3, >( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect | E2, R | R2> => catchCause(self, (cause): Effect.Effect | E2, R2> => { const eb = filter(cause) return Result.isFailure(eb) ? failCause(eb.failure) : f(eb.success, cause) }) ) /** @internal */ export const catch_: { ( f: (e: NoInfer) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (e: NoInfer) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (a: NoInfer) => Effect.Effect ): Effect.Effect => catchCauseFilter(self, findRecoverableError as any, (e: any) => f(e)) as any ) /** @internal */ export const catchNoSuchElement = ( self: Effect.Effect ): Effect.Effect, Exclude, R> => matchEffect(self, { onFailure: (error) => isNoSuchElementError(error) ? succeedNone : fail(error as Exclude), onSuccess: succeedSome }) /** @internal */ export const catchDefect: { ( f: (defect: unknown) => Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, f: (defect: unknown) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (defect: unknown) => Effect.Effect ): Effect.Effect => catchCauseFilter(self, findDefect as any, f as any) as any ) /** @internal */ export const tapCause: { ( f: (cause: NoInfer>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (cause: NoInfer>) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (cause: NoInfer>) => Effect.Effect ): Effect.Effect => catchCause(self, (cause) => andThen(f(cause), failCause(cause))) ) /** @internal */ export const tapCauseIf: { ( predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect => catchCauseIf( self, predicate, (cause) => andThen(f(cause), failCause(cause)) ) ) /** @internal */ export const tapCauseFilter: { >( filter: Filter.Filter, EB, X>, f: (a: EB, cause: Cause.Cause) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect >( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (a: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect } = dual( 3, >( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (a: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect => catchCause(self, (cause) => { const result = filter(cause) if (Result.isFailure(result)) { return failCause(cause) } return andThen(f(result.success, cause), failCause(cause)) }) ) /** @internal */ export const tapError: { ( f: (e: NoInfer) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (e: NoInfer) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (e: NoInfer) => Effect.Effect ): Effect.Effect => tapCauseFilter(self, findError as any, (e: any) => f(e)) as any ) /** @internal */ export const tapErrorTag: { | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1>( k: K, f: ( e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> ) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1 >( self: Effect.Effect, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect ): Effect.Effect } = dual( 3, < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1 >( self: Effect.Effect, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect ): Effect.Effect => { const predicate = Array.isArray(k) ? ((e: E): e is ExtractTag ? K[number] : K> => hasProperty(e, "_tag") && k.includes(e._tag)) : isTagged(k as string) return tapError( self, (error) => predicate(error) ? f(error as ExtractTag ? K[number] : K>) : void_ ) } ) /** @internal */ export const tapDefect: { ( f: (defect: unknown) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (defect: unknown) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (defect: unknown) => Effect.Effect ): Effect.Effect => tapCauseFilter(self, findDefect as any, (_: any) => f(_)) as any ) /** @internal */ export const catchIf: { ( refinement: Predicate.Refinement, EB>, f: (e: EB) => Effect.Effect, orElse?: ((e: Exclude) => Effect.Effect) | undefined ): ( self: Effect.Effect ) => Effect.Effect< A | A2 | Exclude, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3 > ( predicate: Predicate.Predicate>, f: (e: NoInfer) => Effect.Effect, orElse?: ((e: NoInfer) => Effect.Effect) | undefined ): ( self: Effect.Effect ) => Effect.Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> ( self: Effect.Effect, refinement: Predicate.Refinement, EB>, f: (e: EB) => Effect.Effect, orElse?: ((e: Exclude) => Effect.Effect) | undefined ): Effect.Effect< A | A2 | Exclude, E2 | E3 | (A3 extends unassigned ? Exclude : never), R | R2 | R3 > ( self: Effect.Effect, predicate: Predicate.Predicate, f: (e: E) => Effect.Effect, orElse?: ((e: E) => Effect.Effect) | undefined ): Effect.Effect, E2 | E3 | (A3 extends unassigned ? E : never), R | R2 | R3> } = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, predicate: Predicate.Predicate, f: (e: E) => Effect.Effect, orElse?: ((e: E) => Effect.Effect) | undefined ): Effect.Effect => catchCause(self, (cause): Effect.Effect => { const error = findRecoverableError(cause) if (Result.isFailure(error)) return failCause(error.failure) if (!predicate(error.success)) { return orElse ? orElse(error.success as any) : failCause(cause as any as Cause.Cause) } return f(error.success as any) }) ) /** @internal */ export const catchFilter: { ( filter: Filter.Filter, EB, X>, f: (e: EB) => Effect.Effect, orElse?: ((e: X) => Effect.Effect) | undefined ): ( self: Effect.Effect ) => Effect.Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> ( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (e: EB) => Effect.Effect, orElse?: ((e: X) => Effect.Effect) | undefined ): Effect.Effect, E2 | E3 | (A3 extends unassigned ? X : never), R | R2 | R3> } = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (e: EB) => Effect.Effect, orElse?: ((e: X) => Effect.Effect) | undefined ): Effect.Effect => catchCause(self, (cause): Effect.Effect => { const error = findRecoverableError(cause) if (Result.isFailure(error)) return failCause(error.failure) const result = filter(error.success) if (Result.isFailure(result)) { return orElse ? orElse(result.failure as any) : failCause(cause as any as Cause.Cause) } return f(result.success) }) ) /** @internal */ export const catchTag: { < const K extends Tags | Arr.NonEmptyReadonlyArray>, E, A1, E1, R1, A2 = unassigned, E2 = never, R2 = never >( k: K, f: ( e: ExtractTag, K extends Arr.NonEmptyReadonlyArray ? K[number] : K> ) => Effect.Effect, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) | undefined ): ( self: Effect.Effect ) => Effect.Effect< A | A1 | Exclude, | E1 | E2 | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), R | R1 | R2 > < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1, A2 = unassigned, E2 = never, R2 = never >( self: Effect.Effect, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) | undefined ): Effect.Effect< A | A1 | Exclude, | E1 | E2 | (A2 extends unassigned ? ExcludeTag ? K[number] : K> : never), R | R1 | R2 > } = dual( (args) => isEffect(args[0]), < A, E, R, const K extends Tags | Arr.NonEmptyReadonlyArray>, R1, E1, A1, A2 = never, E2 = ExcludeTag ? K[number] : K>, R2 = never >( self: Effect.Effect, k: K, f: (e: ExtractTag ? K[number] : K>) => Effect.Effect, orElse?: | ((e: ExcludeTag ? K[number] : K>) => Effect.Effect) | undefined ): Effect.Effect => { const pred = Array.isArray(k) ? ((e: E): e is any => hasProperty(e, "_tag") && k.includes(e._tag)) : isTagged(k as string) return catchIf(self, pred, f, orElse as any) as any } ) /** @internal */ export const catchTags: { < E, Cases extends (E extends { _tag: string } ? { [K in E["_tag"]]+?: (error: Extract) => Effect.Effect } : {}), A2 = unassigned, E2 = never, R2 = never >( cases: Cases, orElse?: ((e: Exclude) => Effect.Effect) | undefined ): (self: Effect.Effect) => Effect.Effect< | A | Exclude | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? A : never }[keyof Cases], | E2 | (A2 extends unassigned ? Exclude : never) | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? E : never }[keyof Cases], | R | R2 | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? R : never }[keyof Cases] > < R, E, A, Cases extends (E extends { _tag: string } ? { [K in E["_tag"]]+?: (error: Extract) => Effect.Effect } : {}), A2 = unassigned, E2 = never, R2 = never >( self: Effect.Effect, cases: Cases, orElse?: ((e: Exclude) => Effect.Effect) | undefined ): Effect.Effect< | A | Exclude | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? A : never }[keyof Cases], | E2 | (A2 extends unassigned ? Exclude : never) | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? E : never }[keyof Cases], | R | R2 | { [K in keyof Cases]: Cases[K] extends ((...args: Array) => Effect.Effect) ? R : never }[keyof Cases] > } = dual((args) => isEffect(args[0]), (self: Effect.Effect, cases: Record, orElse: any) => { let keys: Array return catchFilter( self, (e) => { keys ??= Object.keys(cases) return hasProperty(e, "_tag") && isString(e["_tag"]) && keys.includes(e["_tag"]) ? Result.succeed(e) : Result.fail(e) }, (e: any) => cases[e["_tag"] as string](e), orElse ) as any }) /** @internal */ export const catchReason: { < K extends Tags, E, RK extends ReasonTags, K>>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( errorTag: K, reasonTag: RK, f: ( reason: ExtractReason, K>, RK>, error: NarrowReason, K>, RK> ) => Effect.Effect, orElse?: | (( reasons: ExcludeReason, K>, RK>, error: OmitReason, K>, RK> ) => Effect.Effect) | undefined ): ( self: Effect.Effect ) => Effect.Effect< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > < A, E, R, K extends Tags, RK extends ReasonTags>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( self: Effect.Effect, errorTag: K, reasonTag: RK, f: ( reason: ExtractReason, RK>, error: NarrowReason, RK> ) => Effect.Effect, orElse?: | (( reasons: ExcludeReason, RK>, error: OmitReason, RK> ) => Effect.Effect) | undefined ): Effect.Effect< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > } = dual( (args) => isEffect(args[0]), < A, E, R, K extends Tags, RK extends ReasonTags>, A2, E2, R2, A3 = unassigned, E3 = never, R3 = never >( self: Effect.Effect, errorTag: K, reasonTag: RK, f: (reason: ExtractReason, RK>, error: ExtractTag) => Effect.Effect, orElse?: | (( reasons: ExcludeReason, RK>, error: OmitReason, RK> ) => Effect.Effect) | undefined ): Effect.Effect< A | A2 | Exclude, ExcludeTag | E2 | E3 | (A3 extends unassigned ? ExtractTag : never), R | R2 | R3 > => catchIf( self, ((e: any) => isTagged(e, errorTag) && hasProperty(e, "reason") && (orElse !== undefined || isTagged(e.reason, reasonTag))) as any, (e: any): Effect.Effect => { const reason = e.reason as any if (isTagged(reason, reasonTag)) return f(reason as any, e) return orElse ? orElse(reason, e) : fail(e) } ) as any ) /** @internal */ export const catchReasons: { < K extends Tags, E, Cases extends { [RK in ReasonTags, K>>]+?: ( reason: ExtractReason, K>, RK>, error: NarrowReason, K>, RK> ) => Effect.Effect }, A2 = unassigned, E2 = never, R2 = never >( errorTag: K, cases: Cases, orElse?: | (( reason: ExcludeReason, K>, Extract>, error: OmitReason, K>, Extract> ) => Effect.Effect) | undefined ): (self: Effect.Effect) => Effect.Effect< | A | Exclude | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? A : never }[ keyof Cases ], | ExcludeTag | E2 | (A2 extends unassigned ? ExtractTag : never) | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? E : never }[ keyof Cases ], | R | R2 | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? R : never }[ keyof Cases ] > < A, E, R, K extends Tags, Cases extends { [RK in ReasonTags>]+?: ( reason: ExtractReason, RK>, error: NarrowReason, RK> ) => Effect.Effect }, A2 = unassigned, E2 = never, R2 = never >( self: Effect.Effect, errorTag: K, cases: Cases, orElse?: | (( reason: ExcludeReason, K>, Extract>, error: OmitReason, K>, Extract> ) => Effect.Effect) | undefined ): Effect.Effect< | A | Exclude | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? A : never }[ keyof Cases ], | ExcludeTag | E2 | (A2 extends unassigned ? ExtractTag : never) | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? E : never }[ keyof Cases ], | R | R2 | { [RK in keyof Cases]: Cases[RK] extends (...args: Array) => Effect.Effect ? R : never }[ keyof Cases ] > } = dual((args) => isEffect(args[0]), (self, errorTag, cases, orElse) => { let keys: Array return catchIf( self, ((e: any) => isTagged(e, errorTag) && hasProperty(e, "reason") && hasProperty(e.reason, "_tag") && isString(e.reason._tag) && (orElse !== undefined || (keys ??= Object.keys(cases)).includes(e.reason._tag))) as any, (e: any) => { const reason = e.reason keys ??= Object.keys(cases) if (keys.includes(reason._tag)) { return (cases as any)[reason._tag](reason, e) } return orElse ? orElse(reason, e) : fail(e) } ) }) /** @internal */ export const unwrapReason: { < K extends Effect.TagsWithReason, E >( errorTag: K ): (self: Effect.Effect) => Effect.Effect | ReasonOf>, R> < A, E, R, K extends Effect.TagsWithReason >( self: Effect.Effect, errorTag: K ): Effect.Effect | ReasonOf>, R> } = dual( 2, < A, E, R, K extends Effect.TagsWithReason >( self: Effect.Effect, errorTag: K ): Effect.Effect | ReasonOf>, R> => catchFilter( self, (e: any) => { if (isTagged(e, errorTag) && hasProperty(e, "reason")) { return Result.succeed(e.reason) } return Result.fail(e) }, fail as any ) as any ) /** @internal */ export const mapError: { ( f: (e: E) => E2 ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (e: E) => E2 ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (e: E) => E2 ): Effect.Effect => catchCause(self, (cause) => failCause(causeMap(cause, f))) ) /** @internal */ export const mapBoth: { ( options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Effect.Effect } = dual(2, ( self: Effect.Effect, options: { readonly onFailure: (e: E) => E2; readonly onSuccess: (a: A) => A2 } ): Effect.Effect => matchCauseEffect(self, { onFailure: (cause) => failCause(causeMap(cause, options.onFailure)), onSuccess: (a) => sync(() => options.onSuccess(a)) })) /** @internal */ export const orDie = ( self: Effect.Effect ): Effect.Effect => catch_(self, die) /** @internal */ export const orElseSucceed: { ( f: (error: NoInfer) => B ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (error: E) => B ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (error: E) => B ): Effect.Effect => catch_(self, (error) => sync(() => f(error))) ) /** @internal */ export const firstSuccessOf = >( effects: Iterable ): Effect.Effect, Effect.Error, Effect.Services> => suspend(() => { const iterator = effects[Symbol.iterator]() let state = iterator.next() if (state.done) { return die(new Error("Received an empty collection of effects")) } function loop(current: IteratorYieldResult): Eff { const next = iterator.next() if (next.done) return current.value return catch_(current.value, (_) => loop(next)) as any } return loop(state) }) /** @internal */ export const eventually = (self: Effect.Effect): Effect.Effect => catch_(self, (_) => flatMap(yieldNow, () => eventually(self))) /** @internal */ export const ignore: < Arg extends Effect.Effect | { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined = { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } >( effectOrOptions: Arg, options?: { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined ) => [Arg] extends [Effect.Effect] ? Effect.Effect : (self: Effect.Effect) => Effect.Effect = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined ): Effect.Effect => { if (!options?.log) { return matchEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) } const logEffect = logWithLevel(options.log === true ? undefined : options.log) return matchCauseEffect(self, { onFailure(cause) { const error = findRecoverableError(cause) return Result.isFailure(error) ? failCause(error.failure) : options.message === undefined ? logEffect(cause) : logEffect(options.message, cause) }, onSuccess: (_) => void_ }) } ) /** @internal */ export const ignoreCause: < Arg extends Effect.Effect | { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined = { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } >( effectOrOptions: Arg, options?: { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined ) => [Arg] extends [Effect.Effect] ? Effect.Effect : (self: Effect.Effect) => Effect.Effect = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly log?: boolean | LogLevel.Severity | undefined readonly message?: string | undefined } | undefined ): Effect.Effect => { if (!options?.log) { return matchCauseEffect(self, { onFailure: (_) => void_, onSuccess: (_) => void_ }) } const logEffect = logWithLevel(options.log === true ? undefined : options.log) return matchCauseEffect(self, { onFailure: (cause) => options.message === undefined ? logEffect(cause) : logEffect(options.message, cause), onSuccess: (_) => void_ }) } ) /** @internal */ export const option = ( self: Effect.Effect ): Effect.Effect, never, R> => match(self, { onFailure: Option.none, onSuccess: Option.some }) /** @internal */ export const result = ( self: Effect.Effect ): Effect.Effect, never, R> => matchEager(self, { onFailure: Result.fail, onSuccess: Result.succeed }) // ---------------------------------------------------------------------------- // pattern matching // ---------------------------------------------------------------------------- /** @internal */ export const matchCauseEffect: { (options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect }): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect => new OnSuccessAndFailureImpl( self, options.onSuccess.length !== 1 ? (a: A) => options.onSuccess(a) : options.onSuccess, options.onFailure.length !== 1 ? (cause: Cause.Cause) => options.onFailure(cause) : options.onFailure ) ) const OnSuccessAndFailureProto = makePrimitiveProto({ op: "OnSuccessAndFailure", [evaluate]: evaluateCont }) const OnSuccessAndFailureImpl = function( this: any, self: Effect.Effect, onSuccess: any, onFailure: any ) { this[args] = self this[contA] = onSuccess this[contE] = onFailure } as unknown as PrimitiveCtor<[self: Effect.Effect, onSuccess: any, onFailure: any]> OnSuccessAndFailureImpl.prototype = OnSuccessAndFailureProto // `match` and `matchCause` keep the caller's handlers object as the frame's // payload and call it from the prototype, so no closure is built per call. const matchSuccess = function(this: any, value: any, fiber: FiberImpl) { const handlers = this.payload return fiber.succeedWith( continuationMarksStack ? handlers.onSuccess(value) : internalCall(() => handlers.onSuccess(value)) ) } const makeMatch = ( op: string, onFailure: (this: any, cause: Cause.Cause, fiber: FiberImpl) => Primitive | Yield ) => { const Proto = makePrimitiveProto({ op, [evaluate]: evaluateCont, [contA]: matchSuccess, [contE]: onFailure }) const MatchImpl = function(this: any, self: Effect.Effect, handlers: any) { this[args] = self this.payload = handlers } as unknown as { new(self: Effect.Effect, handlers: any): Effect.Effect prototype: any } MatchImpl.prototype = Proto return MatchImpl } const MatchImpl = makeMatch("Match", function(cause, fiber) { const error = findRecoverableError(cause) if (Result.isFailure(error)) return failCause(error.failure) as any const handlers = this.payload return fiber.succeedWith( continuationMarksStack ? handlers.onFailure(error.success) : internalCall(() => handlers.onFailure(error.success)) ) }) const MatchCauseImpl = makeMatch("MatchCause", function(cause, fiber) { const handlers = this.payload return fiber.succeedWith( continuationMarksStack ? handlers.onFailure(cause) : internalCall(() => handlers.onFailure(cause)) ) }) /** @internal */ export const matchCause: { (options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (a: A) => A3 }): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (a: A) => A3 } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (a: A) => A3 } ): Effect.Effect => new MatchCauseImpl(self, options) ) /** @internal */ export const matchEffect: { (options: { readonly onFailure: (e: E) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect }): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (e: E) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (e: E) => Effect.Effect readonly onSuccess: (a: A) => Effect.Effect } ): Effect.Effect => matchCauseEffect(self, { onFailure: (cause) => { const error = findRecoverableError(cause) return Result.isFailure(error) ? failCause(error.failure) : options.onFailure(error.success) }, onSuccess: options.onSuccess }) ) /** @internal */ export const match: { (options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 }): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect => new MatchImpl(self, options) ) /** @internal */ export const matchEager: { (options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 }): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (error: E) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect => { if (effectIsExit(self)) { if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) const error = findRecoverableError(self.cause) if (Result.isFailure(error)) return exitFailCause(error.failure) return exitSucceed(options.onFailure(error.success)) } return match(self, options) } ) /** @internal */ export const matchCauseEager: { (options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (value: A) => A3 }): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly onFailure: (cause: Cause.Cause) => A2 readonly onSuccess: (value: A) => A3 } ): Effect.Effect => { if (effectIsExit(self)) { if (self._tag === "Success") return exitSucceed(options.onSuccess(self.value)) return exitSucceed(options.onFailure(self.cause)) } return matchCause(self, options) } ) /** @internal */ export const exit = (self: Effect.Effect): Effect.Effect, never, R> => effectIsExit(self) ? exitSucceed(self) : exitPrimitive(self) const exitPrimitive: (self: Effect.Effect) => Effect.Effect, never, R> = makePrimitive({ op: "Exit", [evaluate](fiber): Primitive { fiber._stack.push(this) return this[args] as any }, [contA](value, fiber, exit) { return fiber.succeedWith(exit ?? exitSucceed(value)) }, [contE](cause, fiber, exit) { return fiber.succeedWith(exit ?? exitFailCause(cause)) } }) // ---------------------------------------------------------------------------- // Condition checking // ---------------------------------------------------------------------------- /** @internal */ export const isFailure: (self: Effect.Effect) => Effect.Effect = matchEager({ onFailure: () => true, onSuccess: () => false }) /** @internal */ export const isSuccess: (self: Effect.Effect) => Effect.Effect = matchEager({ onFailure: () => false, onSuccess: () => true }) // ---------------------------------------------------------------------------- // delays & timeouts // ---------------------------------------------------------------------------- /** @internal */ export const delay: { ( duration: Duration.Input ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect } = dual( 2, ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect => andThen(sleep(duration), self) ) /** @internal */ export const timeoutOrElse: { (options: { readonly duration: Duration.Input readonly orElse: LazyArg> }): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, options: { readonly duration: Duration.Input readonly orElse: LazyArg> } ): Effect.Effect } = dual( 2, ( self: Effect.Effect, options: { readonly duration: Duration.Input readonly orElse: LazyArg> } ): Effect.Effect => flatMap( timeoutOption(self, options.duration), (option): Effect.Effect => Option.isNone(option) ? options.orElse() : succeed(option.value) ) ) /** @internal */ const timeoutErrorFromDuration = (duration: Duration.Duration): TimeoutError => new TimeoutError(`Operation timed out after '${Duration.format(duration)}'`) /** @internal */ export const timeout: { ( duration: Duration.Input ): ( self: Effect.Effect ) => Effect.Effect ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect } = dual( 2, ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect => { const decoded = Duration.fromInputUnsafe(duration) return timeoutOrElse(self, { duration: decoded, orElse: () => fail(timeoutErrorFromDuration(decoded)) }) } ) /** @internal */ export const timeoutOption: { ( duration: Duration.Input ): ( self: Effect.Effect ) => Effect.Effect, E, R> ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect, E, R> } = dual( 2, ( self: Effect.Effect, duration: Duration.Input ): Effect.Effect, E, R> => raceFirst( asSome(self), as(sleep(duration), Option.none()) ) ) /** @internal */ export const timed = ( self: Effect.Effect ): Effect.Effect<[duration: Duration.Duration, result: A], E, R> => clockWith((clock) => { const start = clock.monotonicTimeNanosUnsafe() return map(self, (a) => [Duration.nanos(clock.monotonicTimeNanosUnsafe() - start), a]) }) // ---------------------------------------------------------------------------- // resources & finalization // ---------------------------------------------------------------------------- /** @internal */ export const ScopeTypeId = "~effect/Scope" /** @internal */ export const ScopeCloseableTypeId = "~effect/Scope/Closeable" /** @internal */ export const scopeTag: Context.Service = Context.Service("effect/Scope") /** @internal */ export const scopeClose = (self: Scope.Scope, exit_: Exit.Exit) => withFiber((fiber) => { fiberEnterUninterruptibleUnsafe(fiber) return scopeCloseUnsafe(self, exit_) ?? void_ }) /** @internal */ export const scopeCloseUnsafe = (self: Scope.Scope, exit_: Exit.Exit) => { const state = self.state if (state._tag === "Closed") return self.state = { _tag: "Closed", exit: exit_ } if (self.parent !== undefined) { scopeRemoveFinalizerUnsafe(self.parent, self) } if (state._tag === "Empty") return if (state.finalizer !== undefined) { return runFinalizer(state.finalizer, exit_) } const finalizers = state.finalizers! if (finalizers.size === 1) { return runFinalizer(finalizers.values().next().value!, exit_) } return scopeCloseFinalizers(self, finalizers, exit_) } const runFinalizer = ( finalizer: (exit: Exit.Exit) => Effect.Effect, exit_: Exit.Exit ): Effect.Effect => { try { return finalizer(exit_) } catch (defect) { return exitDie(defect) } } const combineFinalizerCause = ( exit_: Exit.Exit, finalizer: Effect.Effect ): Effect.Effect => exitIsSuccess(exit_) ? finalizer : catchCause(finalizer, (cause) => failCause(causeElectDefects(causeCombine(exit_.cause, cause)))) const scopeCloseFinalizers = fnUntraced(function*( self: Scope.Scope, finalizers: NonNullable, exit_: Exit.Exit ) { let exits: Array> = [] const fibers: Array> = [] const arr = Array.from(finalizers) const parent = getCurrentFiber()! for (let i = arr.length - 1; i >= 0; i--) { const [key, finalizer] = arr[i] if (self.strategy === "sequential") { exits.push(yield* exit(runFinalizer(finalizer, exit_))) } else if (key !== parent) { // Skip the closing fiber's own `forkIn` / `Fiber.runIn` finalizer, as // running it in another fiber would wait for the closing fiber to exit. fibers.push(forkUnsafe(parent, runFinalizer(finalizer, exit_), true, true, "inherit")) } } if (fibers.length > 0) { exits = yield* fiberAwaitAll(fibers) } return yield* exitAsVoidAll(exits) }) /** @internal */ export const scopeFork = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") => sync(() => scopeForkUnsafe(scope, finalizerStrategy)) /** @internal */ export const scopeForkUnsafe = (scope: Scope.Scope, finalizerStrategy?: "sequential" | "parallel") => { const child = makeScope(finalizerStrategy, scope) if (scope.state._tag === "Closed") { child.state = scope.state return child } scopeAddFinalizerUnsafe(scope, child, (exit) => scopeClose(child, exit)) return child } /** @internal */ export const scopeAddFinalizerExit = ( scope: Scope.Scope, finalizer: (exit: Exit.Exit) => Effect.Effect ): Effect.Effect => { return suspend(() => { if (scope.state._tag === "Closed") { return finalizer(scope.state.exit) } scopeAddFinalizerUnsafe(scope, {}, finalizer) return void_ }) } /** @internal */ export const scopeAddFinalizer = ( scope: Scope.Scope, finalizer: Effect.Effect ): Effect.Effect => scopeAddFinalizerExit(scope, constant(finalizer)) /** @internal */ export const scopeAddFinalizerUnsafe = ( scope: Scope.Scope, key: {}, finalizer: (exit: Exit.Exit) => Effect.Effect ): void => { if (scope.state._tag === "Empty") { scope.state = { _tag: "Open", finalizerKey: key, finalizer, finalizers: undefined } } else if (scope.state._tag === "Open") { const state = scope.state if (state.finalizer !== undefined) { state.finalizers = new Map([[state.finalizerKey!, state.finalizer]]) state.finalizerKey = undefined state.finalizer = undefined state.finalizers.set(key, finalizer) } else { state.finalizers!.set(key, finalizer) } } } /** @internal */ export const scopeRemoveFinalizerUnsafe = ( scope: Scope.Scope, key: {} ): void => { if (scope.state._tag !== "Open") return const state = scope.state if (state.finalizerKey === key) { scope.state = constScopeEmpty } else if (state.finalizers !== undefined) { state.finalizers.delete(key) if (state.finalizers.size === 0) { scope.state = constScopeEmpty } } } /** @internal */ export const scopeMakeUnsafe = (finalizerStrategy?: "sequential" | "parallel"): Scope.Closeable => makeScope(finalizerStrategy, undefined) const makeScope = ( finalizerStrategy: "sequential" | "parallel" = "sequential", parent: Scope.Scope | undefined ): Scope.Closeable => ({ [ScopeCloseableTypeId]: ScopeCloseableTypeId, [ScopeTypeId]: ScopeTypeId, strategy: finalizerStrategy, parent, state: constScopeEmpty }) const constScopeEmpty = { _tag: "Empty" } as const /** @internal */ export const scopeMake = (finalizerStrategy?: "sequential" | "parallel"): Effect.Effect => sync(() => scopeMakeUnsafe(finalizerStrategy)) /** @internal */ export const scope: Effect.Effect = scopeTag /** @internal */ export const provideScope: { (value: Scope.Scope): (self: Effect.Effect) => Effect.Effect> (self: Effect.Effect, value: Scope.Scope): Effect.Effect> } = provideService(scopeTag) /** @internal */ export const scoped = (self: Effect.Effect): Effect.Effect> => withFiber((fiber) => { const prev = fiber.context const scope = scopeMakeUnsafe() fiber.setContext(Context.add(fiber.context, scopeTag, scope)) onExitUnsafe(fiber, (exit) => { fiber.setContext(prev) return scopeCloseUnsafe(scope, exit) }) return self }) as any /** @internal */ export const scopeUse: { ( scope: Scope.Closeable ): (self: Effect.Effect) => Effect.Effect> (self: Effect.Effect, scope: Scope.Closeable): Effect.Effect> } = dual( 2, (self: Effect.Effect, scope: Scope.Closeable): Effect.Effect> => onExit(provideScope(self, scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_)) ) /** @internal */ export const scopedWith = ( f: (scope: Scope.Scope) => Effect.Effect ): Effect.Effect => suspend(() => { const scope = scopeMakeUnsafe() return onExit(f(scope), (exit) => suspend(() => scopeCloseUnsafe(scope, exit) ?? void_)) }) /** @internal */ export const acquireRelease = ( acquire: Effect.Effect, release: (a: A, exit: Exit.Exit) => Effect.Effect, options?: { readonly interruptible?: boolean } ): Effect.Effect => contextWith((context: Context.Context) => uninterruptibleMask((restore) => flatMap( scope, (scope) => tap( options?.interruptible ? restore(acquire) : acquire, (a) => scopeAddFinalizerExit(scope, (exit) => provideContext(release(a, exit), context)) ) ) ) ) /** @internal */ export const addFinalizer = ( finalizer: (exit: Exit.Exit) => Effect.Effect ): Effect.Effect => flatMap( scope, (scope) => contextWith((context: Context.Context) => scopeAddFinalizerExit(scope, (exit) => provideContext(finalizer(exit), context)) ) ) const OnExitImpl = (function() { const Proto = makePrimitiveProto({ op: "OnExit", [evaluate](this: any, fiber: FiberImpl) { fiber._stack.push(this) return this.effect }, [contAll](this: any, fiber) { if (fiber.interruptible && this.interruptible !== true) { fiber._stack.push(setInterruptibleTrue) fiber.interruptible = false } }, [contA](this: any, value, _, exit) { exit ??= exitSucceed(value) let eff: Effect.Effect | undefined try { eff = this.onExit(exit) } catch (defect) { eff = exitDie(defect) } return eff ? flatMap(eff, (_) => exit) : exit }, [contE](this: any, cause, _, exit) { exit ??= exitFailCause(cause) let eff: Effect.Effect | undefined try { eff = this.onExit(exit) } catch (defect) { eff = exitDie(defect) } return eff ? flatMap(combineFinalizerCause(exit, eff), (_) => exit) : exit } }) const OnExitImpl = function(this: any, effect: any, onExit: any, interruptible: any) { this.effect = effect this.onExit = onExit this.interruptible = interruptible } as unknown as PrimitiveCtor<[effect: any, onExit: any, interruptible: any]> OnExitImpl.prototype = Proto return OnExitImpl })() /** @internal */ export const onExitPrimitive: ( self: Effect.Effect, f: (exit: Exit.Exit) => Effect.Effect | undefined, interruptible?: boolean ) => Effect.Effect = (effect, onExit, interruptible) => new OnExitImpl(effect, onExit, interruptible) as any /** * Pushes an exit finalizer for the current `withFiber` evaluation. Only call inside `withFiber`. * @internal */ export const onExitUnsafe = ( fiber: Fiber.Fiber, f: (exit: Exit.Exit) => Effect.Effect | undefined ): void => { ;(fiber as FiberImpl)._stack.push(new OnExitImpl(undefined, f, undefined)) } /** @internal */ export const onExit: { ( f: (exit: Exit.Exit) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (exit: Exit.Exit) => Effect.Effect ): Effect.Effect } = dual(2, onExitPrimitive) /** @internal */ export const ensuring: { ( finalizer: Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, finalizer: Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, finalizer: Effect.Effect ): Effect.Effect => onExit(self, (_) => finalizer) ) /** @internal */ export const onExitIf: { ( predicate: Predicate.Predicate, NoInfer>>, f: (exit: Exit.Exit, NoInfer>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate, NoInfer>>, f: (exit: Exit.Exit, NoInfer>) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, predicate: Predicate.Predicate, NoInfer>>, f: (exit: Exit.Exit, NoInfer>) => Effect.Effect ): Effect.Effect => onExit(self, (exit) => { if (!predicate(exit)) { return void_ } return f(exit) }) ) /** @internal */ export const onExitFilter: { ( filter: Filter.Filter, NoInfer>, B, X>, f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, filter: Filter.Filter, NoInfer>, B, X>, f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, filter: Filter.Filter, NoInfer>, B, X>, f: (b: B, exit: Exit.Exit, NoInfer>) => Effect.Effect ): Effect.Effect => onExit(self, (exit) => { const b = filter(exit) return Result.isFailure(b) ? void_ : f(b.success, exit) }) ) /** @internal */ export const onError: { ( f: (cause: Cause.Cause>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, f: (cause: Cause.Cause>) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, f: (cause: Cause.Cause>) => Effect.Effect ): Effect.Effect => onExitFilter(self, exitFilterCause as any, f as any) as any ) /** @internal */ export const onErrorIf: { ( predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, predicate: Predicate.Predicate>, f: (cause: Cause.Cause) => Effect.Effect ): Effect.Effect => onExitIf( self, (exit): exit is Exit.Failure => { if (exit._tag !== "Failure") { return false } return predicate(exit.cause) }, (exit) => f((exit as Exit.Failure).cause) ) as any ) /** @internal */ export const onErrorFilter: { ( filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect } = dual( 3, ( self: Effect.Effect, filter: Filter.Filter, EB, X>, f: (failure: EB, cause: Cause.Cause) => Effect.Effect ): Effect.Effect => onExit(self, (exit) => { if (exit._tag !== "Failure") { return void_ } const result = filter(exit.cause) return Result.isFailure(result) ? void_ : f(result.success, exit.cause) }) ) /** @internal */ export const onInterrupt: { ( finalizer: (interruptors: ReadonlySet) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, finalizer: (interruptors: ReadonlySet) => Effect.Effect ): Effect.Effect } = dual( 2, ( self: Effect.Effect, finalizer: (interruptors: ReadonlySet) => Effect.Effect ): Effect.Effect => onErrorFilter(causeFilterInterruptors as any, finalizer)(self) as any ) /** @internal */ export const acquireUseRelease = ( acquire: Effect.Effect, use: (a: Resource) => Effect.Effect, release: (a: Resource, exit: Exit.Exit) => Effect.Effect ): Effect.Effect => uninterruptibleMask((restore) => flatMap(acquire, (a) => onExitPrimitive( suspend(() => restore(use(a))), (exit) => release(a, exit), true )) ) /** @internal */ export const acquireDisposable = ( acquire: Effect.Effect ): Effect.Effect => acquireRelease(acquire, (resource) => hasProperty(resource, Symbol.asyncDispose) ? promise(() => resource[Symbol.asyncDispose]()) : sync(() => resource[Symbol.dispose]())) // ---------------------------------------------------------------------------- // Caching // ---------------------------------------------------------------------------- /** @internal */ export const cachedInvalidateWithTTL: { (timeToLive: (exit: Exit.Exit) => Duration.Input): ( self: Effect.Effect ) => Effect.Effect<[Effect.Effect, Effect.Effect]> (timeToLive: Duration.Input): ( self: Effect.Effect ) => Effect.Effect<[Effect.Effect, Effect.Effect]> (timeToLive: Duration.Input | ((exit: Exit.Exit) => Duration.Input)): ( self: Effect.Effect ) => Effect.Effect<[Effect.Effect, Effect.Effect]> ( self: Effect.Effect, timeToLive: Duration.Input | ((exit: Exit.Exit) => Duration.Input) ): Effect.Effect<[Effect.Effect, Effect.Effect]> } = dual(2, ( self: Effect.Effect, ttl: Duration.Input | ((exit: Exit.Exit) => Duration.Input) ): Effect.Effect<[Effect.Effect, Effect.Effect]> => sync(() => makeCachedUnsafe( self, typeof ttl === "function" ? (exit: Exit.Exit) => Duration.toMillis(Duration.fromInputUnsafe(ttl(exit))) : constant(Duration.toMillis(Duration.fromInputUnsafe(ttl))) ) )) const infiniteTTL = constant(Infinity) interface CachedRun { readonly fiber: FiberImpl awaiters: number done: boolean } const makeCachedUnsafe = ( self: Effect.Effect, ttlMillis: (exit: Exit.Exit) => number ): [Effect.Effect, Effect.Effect] => { let expiresAt = 0 let exit: Exit.Exit | undefined let current: CachedRun | undefined const join = (fiber: Fiber.Fiber, run: CachedRun): Effect.Effect => { run.awaiters++ onExitUnsafe(fiber, () => { // Abandon the run once every caller has left, unless its source already finished. if (--run.awaiters > 0 || run.done) return // Detach before interruption so new callers start a fresh run. if (current === run) current = undefined return fiberInterrupt(run.fiber) }) return fiberJoin(run.fiber) } return [ withFiber((fiber) => { if (current !== undefined) { // A run joining itself is not an awaiter, so external callers can still abandon it. return fiber === current.fiber ? fiberJoin(current.fiber) : join(fiber, current) } if ( exit !== undefined && (expiresAt === Infinity || fiber.getRef(ClockRef).currentTimeMillisUnsafe() < expiresAt) ) { return exit } exit = undefined const clock = fiber.getRef(ClockRef) const run: CachedRun = { fiber: new FiberImpl(fiber.context), awaiters: 0, done: false } current = run // Join before evaluating, which may synchronously re-enter and interrupt other awaiters. const joined = join(fiber, run) run.fiber.evaluate( onExitPrimitive(self, (exit_) => { run.done = true // A detached run must not publish its result or clear a replacement run. if (current !== run) return current = undefined // Interruption is abandonment, so it is never cached. if (exitHasInterrupts(exit_)) return const duration = ttlMillis(exit_) expiresAt = duration === Infinity ? Infinity : clock.currentTimeMillisUnsafe() + duration exit = exit_ }) as any ) return run.fiber._exit ?? joined }), sync(() => { // Existing callers keep the in-flight run; new callers start a fresh one. current = undefined exit = undefined }) ] } /** @internal */ export const cachedWithTTL: { ( timeToLive: (exit: Exit.Exit) => Duration.Input ): (self: Effect.Effect) => Effect.Effect> ( timeToLive: Duration.Input ): (self: Effect.Effect) => Effect.Effect> ( timeToLive: Duration.Input | ((exit: Exit.Exit) => Duration.Input) ): (self: Effect.Effect) => Effect.Effect> ( self: Effect.Effect, timeToLive: Duration.Input | ((exit: Exit.Exit) => Duration.Input) ): Effect.Effect> } = dual( 2, ( self: Effect.Effect, timeToLive: Duration.Input | ((exit: Exit.Exit) => Duration.Input) ): Effect.Effect> => map(cachedInvalidateWithTTL(self, timeToLive), (tuple) => tuple[0]) ) /** @internal */ export const cached = (self: Effect.Effect): Effect.Effect> => sync(() => makeCachedUnsafe(self, infiniteTTL)[0]) // ---------------------------------------------------------------------------- // interruption // ---------------------------------------------------------------------------- /** @internal */ export const interrupt: Effect.Effect = withFiber((fiber) => failCause(causeInterrupt(fiber.id))) /** @internal */ export const uninterruptible = ( self: Effect.Effect ): Effect.Effect => withFiber((fiber) => { if (!fiber.interruptible) return self fiber.interruptible = false fiber._stack.push(setInterruptibleTrue) return self }) const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({ op: "SetInterruptible", [contAll](fiber) { fiber.interruptible = this[args] if (fiber._interruptedCause && fiber.interruptible) { return () => failCause(fiber._interruptedCause!) } } }) const setInterruptibleTrue = setInterruptible(true) const setInterruptibleFalse = setInterruptible(false) const setFiberInterruptible = (fiber: FiberImpl): Effect.Effect | undefined => { fiber.interruptible = true fiber._stack.push(setInterruptibleFalse) if (fiber._interruptedCause) return failCause(fiber._interruptedCause) } /** * Makes the current fiber uninterruptible for the returned effect without an * extra primitive. Call only within `withFiber`. * * @internal */ export const fiberEnterUninterruptibleUnsafe = (fiber: Fiber.Fiber): void => { const impl = fiber as FiberImpl if (!impl.interruptible) return impl.interruptible = false impl._stack.push(setInterruptibleTrue) } /** * Makes the current fiber interruptible for the returned effect without an * extra primitive. Call only within `withFiber` and return any pending * interruption it produces. * * @internal */ export const fiberEnterInterruptibleUnsafe = ( fiber: Fiber.Fiber ): Effect.Effect | undefined => { const impl = fiber as FiberImpl if (impl.interruptible) return undefined return setFiberInterruptible(impl) } /** @internal */ export const interruptible = ( self: Effect.Effect ): Effect.Effect => withFiber((fiber) => { if (fiber.interruptible) return self return setFiberInterruptible(fiber) ?? self }) /** @internal */ export const uninterruptibleMask = ( f: ( restore: ( effect: Effect.Effect ) => Effect.Effect ) => Effect.Effect ): Effect.Effect => withFiber((fiber) => { if (!fiber.interruptible) return f(identity) fiber.interruptible = false fiber._stack.push(setInterruptibleTrue) return f(interruptible) }) /** @internal */ export const interruptibleMask = ( f: ( restore: ( effect: Effect.Effect ) => Effect.Effect ) => Effect.Effect ): Effect.Effect => withFiber((fiber) => { if (fiber.interruptible) return f(identity) const interrupted = setFiberInterruptible(fiber) const effect = f(uninterruptible) return interrupted ?? effect }) /** @internal */ export const abortSignal: Effect.Effect = map( acquireRelease( sync(() => new AbortController()), (controller) => sync(() => controller.abort()) ), (_) => _.signal ) // ======================================================================== // collecting & elements // ======================================================================== /** @internal */ export const all = < const Arg extends | Iterable> | Record>, O extends { readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined readonly mode?: "default" | "result" | undefined } >( arg: Arg, options?: O ): Effect.All.Return => { if (isIterable(arg)) { return options?.mode === "result" ? (forEach as any)(arg, result, options) : (forEach as any)(arg, identity, options) } else if (options?.discard) { return options.mode === "result" ? (forEach as any)(Object.values(arg), result, options) : (forEach as any)(Object.values(arg), identity, options) } return suspend(() => map( forEach( Object.entries(arg), ([key, effect]) => map(options?.mode === "result" ? result(effect) : effect, (value) => [key, value] as const), { concurrency: options?.concurrency } ), Object.fromEntries ) ) as any } /** @internal */ export const partition: { ( f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } ): (elements: Iterable) => Effect.Effect<[passes: Array, fails: Array], never, R> ( elements: Iterable, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect<[passes: Array, fails: Array], never, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect<[passes: Array, fails: Array], never, R> => map( forEach(elements, (a, i) => result(f(a, i)), options), (results) => Arr.partition(results, identity) ) ) /** @internal */ export const reduce: { ( zero: LazyArg, f: (z: Z, a: A, i: number) => Effect.Effect ): (elements: Iterable) => Effect.Effect ( elements: Iterable, zero: LazyArg, f: (z: Z, a: A, i: number) => Effect.Effect ): Effect.Effect } = dual( 3, ( elements: Iterable, zero: LazyArg, f: (z: Z, a: A, i: number) => Effect.Effect ) => { const arr = Arr.fromIterable(elements) if (arr.length === 0) return sync(zero) return suspend(() => { let index = 0 let state = zero() return map( whileLoop({ while: () => index < arr.length, body: () => f(state, arr[index], index), step(next) { state = next index++ } }), () => state ) }) } ) /** @internal */ export const validate: { ( f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } | undefined ): (elements: Iterable) => Effect.Effect, Arr.NonEmptyArray, R> ( f: (a: A, i: number) => Effect.Effect, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): (elements: Iterable) => Effect.Effect, R> ( elements: Iterable, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: false | undefined } | undefined ): Effect.Effect, Arr.NonEmptyArray, R> ( elements: Iterable, f: (a: A, i: number) => Effect.Effect, options: { readonly concurrency?: Concurrency | undefined readonly discard: true } ): Effect.Effect, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined } | undefined ): Effect.Effect | void, Arr.NonEmptyArray, R> => flatMap( partition(elements, f, { concurrency: options?.concurrency }), ([passes, fails]) => { if (Arr.isArrayNonEmpty(fails)) { return fail(fails) } return options?.discard ? void_ : succeed(passes) } ) ) /** @internal */ export const findFirst: { ( predicate: (a: NoInfer, i: number) => Effect.Effect ): (elements: Iterable) => Effect.Effect, E, R> ( elements: Iterable, predicate: (a: NoInfer, i: number) => Effect.Effect ): Effect.Effect, E, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, predicate: (a: A, i: number) => Effect.Effect ): Effect.Effect, E, R> => suspend(() => { const iterator = elements[Symbol.iterator]() const next = iterator.next() if (!next.done) { return findFirstLoop(iterator, 0, predicate, next.value) } return succeed(Option.none()) }) ) const findFirstLoop = ( iterator: Iterator, index: number, predicate: (a: A, i: number) => Effect.Effect, value: A ): Effect.Effect, E, R> => flatMap(predicate(value, index), (keep) => { if (keep) { return succeed(Option.some(value)) } const next = iterator.next() if (!next.done) { return findFirstLoop(iterator, index + 1, predicate, next.value) } return succeed(Option.none()) }) /** @internal */ export const findFirstFilter: { ( filter: (input: NoInfer, i: number) => Effect.Effect, E, R> ): (elements: Iterable) => Effect.Effect, E, R> ( elements: Iterable, filter: (input: NoInfer, i: number) => Effect.Effect, E, R> ): Effect.Effect, E, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, filter: (input: A, i: number) => Effect.Effect, E, R> ): Effect.Effect, E, R> => suspend(() => { const iterator = elements[Symbol.iterator]() const next = iterator.next() if (!next.done) { return findFirstFilterLoop(iterator, 0, filter, next.value) } return succeed(Option.none()) }) ) const findFirstFilterLoop = ( iterator: Iterator, index: number, filter: (input: A, i: number) => Effect.Effect, E, R>, value: A ): Effect.Effect, E, R> => flatMap(filter(value, index), (result) => { if (Result.isSuccess(result)) { return succeed(Option.some(result.success)) } const next = iterator.next() if (!next.done) { return findFirstFilterLoop(iterator, index + 1, filter, next.value) } return succeed(Option.none()) }) /** @internal */ export const whileLoop: (options: { readonly while: LazyArg readonly body: LazyArg> readonly step: (a: A) => void }) => Effect.Effect = makePrimitive({ op: "While", [contA](value, fiber) { this[args].step(value) if (this[args].while()) { fiber._stack.push(this) return this[args].body() } return exitVoid }, [evaluate](fiber) { if (this[args].while()) { fiber._stack.push(this) return this[args].body() } return exitVoid } }) /** @internal */ export const forEach: { = Iterable, const Discard extends boolean = false>( f: (a: A, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: Discard | undefined } | undefined ): ( self: [S] extends [never] ? Iterable : S ) => Effect.Effect : void, E, R> , const Discard extends boolean = false>( self: S, f: (a: Arr.ReadonlyArray.Infer, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: Discard | undefined } | undefined ): Effect.Effect : void, E, R> } = dual((args) => typeof args[1] === "function", ( iterable: Iterable, f: (a: A, index: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined readonly discard?: boolean | undefined } ): Effect.Effect => suspend(() => { const concurrency = resolveConcurrency(options?.concurrency) if (concurrency === 1) { return forEachSequential(iterable, f, options) } const items = Arr.fromIterable(iterable) let length = items.length if (length === 0) { return options?.discard ? void_ : succeed([]) } const out: Array | undefined = options?.discard ? undefined : new Array(length) const eff = forEachConcurrent({ f, out }, items, { concurrency }) return eff ? as(eff, out as any) : succeed(out as any) })) /** @internal */ export const head = ( self: Effect.Effect, E, R> ): Effect.Effect => flatMap(self, (elements) => { const result = elements[Symbol.iterator]().next() return result.done ? fail(new NoSuchElementError()) : succeed(result.value) }) const forEachSequential = ( iterable: Iterable, f: (a: A, index: number) => Effect.Effect, options?: { readonly discard?: boolean | undefined } ) => suspend(() => { const out: Array | undefined = options?.discard ? undefined : [] const iterator = iterable[Symbol.iterator]() let state = iterator.next() let index = 0 return as( whileLoop({ while: () => !state.done, body: () => f(state.value!, index++), step: (b) => { if (out) out.push(b) state = iterator.next() } }), out ) }) /** @internal */ export const resolveConcurrency = (concurrency: Concurrency | undefined): number => concurrency === "unbounded" ? Number.POSITIVE_INFINITY : Math.max(1, concurrency ?? 1) type IterateOptions = { readonly onItem: (state: S, item: A, index: number) => Effect.Effect readonly step: (state: NoInfer, item: A, exit: Exit.Exit, index: number) => Exit.Exit | void } /** @internal */ export const iterateEager = () => (options: IterateOptions): ( initialState: S, items: ReadonlyArray, start?: number, end?: number ) => Effect.Effect | undefined => { const onItem = options.onItem const step = options.step const resumeSequential = ( state: S, items: ReadonlyArray, index: number, end: number, effect: Effect.Effect ): Effect.Effect => flatMap( exit(effect), (itemExit) => step(state, items[index], itemExit, index) ?? runSequential(state, items, index + 1, end) ?? void_ ) const runSequential = ( state: S, items: ReadonlyArray, index = 0, end = items.length ): Effect.Effect | undefined => { for (; index < end; index++) { const item = items[index] const effect = onItem(state, item, index) if (!effectIsExit(effect)) { return resumeSequential(state, items, index, end, effect) } const terminal = step(state, item, effect, index) if (terminal) return terminal._tag === "Failure" ? terminal : undefined } } return runSequential } const iterateConcurrentImpl = (options: IterateOptions) => { const onItem = options.onItem const step = options.step return ( state: S, items: ReadonlyArray, opts: { readonly concurrency: number; readonly end?: number | undefined } ): Effect.Effect | undefined => { let index = 0 const end = opts.end ?? items.length const concurrency = opts.concurrency let done = false let parentFiber: Fiber.Fiber | undefined let fibers: Set> | undefined let resume: ((effect: Effect.Effect) => void) | undefined let terminal: Exit.Exit | void let effect: Effect.Effect | undefined const failDefect = (error: unknown): Effect.Effect => { const defect = exitDie(error) terminal = defect done = true return fibers && fibers.size > 0 ? flatMap(uninterruptible(fiberInterruptAll(Array.from(fibers))), () => terminal ?? defect) : defect } const go = (): Effect.Effect | undefined => { let paused = false for (; !terminal && index < end; index++) { const item = items[index] const eff = effect ?? onItem(state, item, index) if (effectIsExit(eff)) { terminal = step(state, item, eff, index) if (terminal) break } else if (!parentFiber) { return callback((cb) => { parentFiber = getCurrentFiber()! fibers = new Set() effect = eff resume = cb let result: Effect.Effect | undefined try { result = go() } catch (error) { return cb(failDefect(error)) } if (result) return cb(result) return suspend(() => { terminal ??= exitVoid return flatMap( fibers ? fiberInterruptAll(fibers) : void_, () => terminal?._tag === "Failure" ? terminal : void_ ) }) }) } else { // Clear the effect cached before the parent fiber was available. effect = undefined const fiber = forkUnsafe(parentFiber, eff, true, true, "inherit") if (fiber._exit) { terminal = step(state, item, fiber._exit, index) if (terminal) break continue } fibers!.add(fiber) const currentIndex = index fiber.addObserver((exit) => { fibers!.delete(fiber) try { if (terminal) { if (exit._tag === "Failure") { const reasons = exit.cause.reasons.filter((reason) => reason._tag !== "Interrupt") if (reasons.length > 0) { const cause = causeFromReasons(reasons) terminal = exitFailCause( terminal._tag === "Failure" ? causeElectDefects(causeCombine(terminal.cause, cause)) : cause ) } } } else { const result = step(state, item, exit, currentIndex) if (result) { terminal = result go() } } if (paused) { const eff = go() if (eff) resume!(eff) } else if (done && fibers!.size === 0) { resume!(terminal ?? void_) } } catch (error) { resume!(failDefect(error)) } }) if (fibers!.size < concurrency) continue paused = true index++ return } } done = true if (terminal) { if (fibers && fibers.size > 0) { const annotations = fiberStackAnnotations(parentFiber!) fibers.forEach((f) => f.interruptUnsafe(parentFiber!.id, annotations)) return } if (resume || terminal._tag === "Failure") { return terminal } } else if (resume) { if (!fibers) { return exitVoid } else if (fibers.size === 0) { resume(void_) } } } return go() } } /** @internal */ export const iterateConcurrent = () => (options: IterateOptions) => iterateConcurrentImpl(options) const forEachConcurrent = iterateConcurrentImpl({ onItem( state: { readonly f: (a: any, i: number) => Effect.Effect readonly out: Array | undefined }, item, index ) { return state.f(item, index) }, step(state, _, exit, index) { if (exit._tag === "Failure") return exit else if (state.out) { state.out[index] = exit.value } } }) /** @internal */ export const filterOrElse: { ( refinement: Predicate.Refinement, B>, orElse: (a: EqualsWith, Exclude, B>>) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( predicate: Predicate.Predicate>, orElse: (a: NoInfer) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, refinement: Predicate.Refinement, orElse: (a: EqualsWith>) => Effect.Effect ): Effect.Effect ( self: Effect.Effect, predicate: Predicate.Predicate>, orElse: (a: NoInfer) => Effect.Effect ): Effect.Effect } = dual(3, ( self: Effect.Effect, predicate: Predicate.Predicate, orElse: (a: A) => Effect.Effect ): Effect.Effect => flatMap( self, (a) => predicate(a) ? succeed(a) : orElse(a) )) /** @internal */ export const filterMapOrElse: { ( filter: Filter.Filter, B, X>, orElse: (x: X) => Effect.Effect ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, filter: Filter.Filter, B, X>, orElse: (x: X) => Effect.Effect ): Effect.Effect } = dual(3, ( self: Effect.Effect, filter: Filter.Filter, B, X>, orElse: (x: X) => Effect.Effect ): Effect.Effect => flatMap( self, (a) => { const result = filter(a) return (Result.isFailure(result) ? orElse(result.failure) : succeed(result.success)) as Effect.Effect } )) /** @internal */ export const filterMapOrFail: { ( filter: Filter.Filter, B, X>, orFailWith: (x: X) => E2 ): (self: Effect.Effect) => Effect.Effect ( filter: Filter.Filter, B, X> ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, filter: Filter.Filter, B, X>, orFailWith: (x: X) => E2 ): Effect.Effect ( self: Effect.Effect, filter: Filter.Filter, B, X> ): Effect.Effect } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, filter: Filter.Filter, B, X>, orFailWith?: (x: X) => E2 ): Effect.Effect => filterMapOrElse( self, filter, orFailWith ? (x: X) => fail(orFailWith(x)) : () => fail(new NoSuchElementError() as E2) )) /** @internal */ export const filter: { ( refinement: Predicate.Refinement, B> ): (elements: Iterable) => Effect.Effect> ( predicate: Predicate.Predicate> ): (elements: Iterable) => Effect.Effect> ( predicate: (a: NoInfer, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } ): (iterable: Iterable) => Effect.Effect, E, R> ( elements: Iterable, refinement: Predicate.Refinement ): Effect.Effect> ( elements: Iterable, predicate: Predicate.Predicate ): Effect.Effect> ( iterable: Iterable, predicate: (a: NoInfer, i: number) => Effect.Effect, options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect, E, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, predicate: | Predicate.Predicate | ((a: A, i: number) => Effect.Effect), options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect, E, R> => map( forEach( elements, (a, i) => { const result = (predicate as Function)(a, i) if (typeof result === "boolean") { return succeed(result ? Option.some(a) : Option.none()) } return map(result, (keep) => keep ? Option.some(a) : Option.none()) }, options ), Arr.getSomes ) ) /** @internal */ export const filterMap: { ( filter: Filter.Filter, B, X> ): (elements: Iterable) => Effect.Effect> ( elements: Iterable, filter: Filter.Filter, B, X> ): Effect.Effect> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, filter: Filter.Filter ): Effect.Effect> => suspend(() => { const out: Array = [] for (const a of elements) { const result = filter(a) if (Result.isSuccess(result)) { out.push(result.success) } } return succeed(out) }) ) /** @internal */ export const filterMapEffect: { ( filter: Filter.FilterEffect, B, X, E, R>, options?: { readonly concurrency?: Concurrency | undefined } ): (elements: Iterable) => Effect.Effect, E, R> ( elements: Iterable, filter: Filter.FilterEffect, B, X, E, R>, options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect, E, R> } = dual( (args) => isIterable(args[0]) && !isEffect(args[0]), ( elements: Iterable, filter: Filter.FilterEffect, options?: { readonly concurrency?: Concurrency | undefined } ): Effect.Effect, E, R> => suspend(() => { const out: Array = [] return as( forEach( elements, (a) => map(filter(a), (result) => { if (Result.isSuccess(result)) { out.push(result.success) } }), { discard: true, concurrency: options?.concurrency } ), out ) }) ) // ---------------------------------------------------------------------------- // do notation // ---------------------------------------------------------------------------- /** @internal */ export const Do: Effect.Effect<{}> = succeed({}) /** @internal */ export const bindTo: { ( name: N ): ( self: Effect.Effect ) => Effect.Effect, E, R> ( self: Effect.Effect, name: N ): Effect.Effect, E, R> } = doNotation.bindTo(map) /** @internal */ export const bind: { , B, E2, R2>( name: N, f: (a: NoInfer) => Effect.Effect ): ( self: Effect.Effect ) => Effect.Effect & Record>, E | E2, R | R2> , E, R, B, E2, R2, N extends string>( self: Effect.Effect, name: N, f: (a: NoInfer) => Effect.Effect ): Effect.Effect & Record>, E | E2, R | R2> } = doNotation.bind(map, flatMap) /** @internal */ const let_: { , B>( name: N, f: (a: NoInfer) => B ): ( self: Effect.Effect ) => Effect.Effect & Record>, E, R> , E, R, B, N extends string>( self: Effect.Effect, name: N, f: (a: NoInfer) => B ): Effect.Effect & Record>, E, R> } = doNotation.let_(map) /** @internal */ export { let_ as let } // ---------------------------------------------------------------------------- // fibers & forking // ---------------------------------------------------------------------------- /** @internal */ export const forkChild: { < Arg extends Effect.Effect | { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined = { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } >( effectOrOptions: Arg, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined ): [Arg] extends [Effect.Effect] ? Effect.Effect, never, _R> : (self: Effect.Effect) => Effect.Effect, never, R> } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly startImmediately?: boolean readonly uninterruptible?: boolean | "inherit" } ): Effect.Effect, never, R> => withFiber((fiber) => { interruptChildrenPatch() return succeed(forkUnsafe( fiber, self, options?.startImmediately, false, options?.uninterruptible ?? false )) })) /** @internal */ export const forkUnsafe = ( parent: Fiber.Fiber, effect: Effect.Effect, immediate = false, daemon = false, uninterruptible: boolean | "inherit" = false ): FiberImpl => { const parentRuntime = parent as FiberImpl const interruptible = uninterruptible === "inherit" ? parentRuntime.interruptible : !uninterruptible const child = new FiberImpl(parentRuntime.context, interruptible) if (!daemon) { parentRuntime.children().add(child) child._parent = parentRuntime } if (immediate) { child.evaluate(effect as any) } else { // Preserve the fork context rather than the dispatcher's context. child._asyncContext = captureAsyncContext() parentRuntime.currentDispatcher.scheduleTask(() => child.evaluate(effect as any), 0) } return child } /** @internal */ export const forkDetach: { < Arg extends Effect.Effect | { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined = { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } >( effectOrOptions: Arg, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined ): [Arg] extends [Effect.Effect] ? Effect.Effect, never, _R> : (self: Effect.Effect) => Effect.Effect, never, R> } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly startImmediately?: boolean readonly uninterruptible?: boolean | "inherit" | undefined } ): Effect.Effect, never, R> => withFiber((fiber) => succeed(forkUnsafe(fiber, self, options?.startImmediately, true, options?.uninterruptible)))) /** @internal */ export const awaitAllChildren = ( self: Effect.Effect ): Effect.Effect => withFiber((fiber) => { const initialChildren = fiber._children && new Set(fiber._children) const isInterruptible = fiber.interruptible return onExitPrimitive( self, (_) => { let children = fiber._children if (children === undefined || children.size === 0) { return void_ } else if (initialChildren) { children = Iterable.filter( children, (child: FiberImpl) => !initialChildren.has(child) ) as Set> } const awaitChildren = asVoid(fiberAwaitAll(children)) return isInterruptible ? interruptible(awaitChildren) : awaitChildren } ) }) // Interrupts the attached fiber, unless it is the fiber running the close. It is // keyed by the fiber, so that parallel scopes can skip it on the closing fiber. const fiberScopeFinalizer = (fiber: Fiber.Fiber) => (): Effect.Effect => withFiber((current) => current === fiber ? void_ : fiberInterrupt(fiber)) /** @internal */ export const forkIn: { ( scope: Scope.Scope, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } ): ( self: Effect.Effect ) => Effect.Effect, never, R> ( self: Effect.Effect, scope: Scope.Scope, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } ): Effect.Effect, never, R> } = dual( (args) => isEffect(args[0]), ( self: Effect.Effect, scope: Scope.Scope, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } ): Effect.Effect, never, R> => withFiber((parent) => { // A closed scope gets an unstarted fiber, which is interrupted below const fiber = scope.state._tag === "Closed" ? new FiberImpl(parent.context) : forkUnsafe(parent, self, options?.startImmediately, true, options?.uninterruptible) if (!(fiber as FiberImpl)._exit) { if (scope.state._tag !== "Closed") { scopeAddFinalizerUnsafe(scope, fiber, fiberScopeFinalizer(fiber)) fiber.addObserver(() => scopeRemoveFinalizerUnsafe(scope, fiber)) } else { fiber.interruptUnsafe(parent.id, fiberStackAnnotations(parent)) } } return succeed(fiber) }) ) /** @internal */ export const forkScoped: { < Arg extends Effect.Effect | { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined = { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } >( effectOrOptions: Arg, options?: { readonly startImmediately?: boolean | undefined readonly uninterruptible?: boolean | "inherit" | undefined } | undefined ): [Arg] extends [Effect.Effect] ? Effect.Effect, never, _R | Scope.Scope> : (self: Effect.Effect) => Effect.Effect, never, R | Scope.Scope> } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, options?: { readonly startImmediately?: boolean readonly uninterruptible?: boolean | "inherit" } ): Effect.Effect, never, R | Scope.Scope> => flatMap(scope, (scope) => forkIn(self, scope, options))) // ---------------------------------------------------------------------------- // execution // ---------------------------------------------------------------------------- /** @internal */ export const runForkWith = (context: Context.Context) => ( effect: Effect.Effect, options?: Effect.RunOptions | undefined ): Fiber.Fiber => { const fiber = new FiberImpl( options?.scheduler ? Context.add(context, Scheduler.Scheduler, options.scheduler) : context, options?.uninterruptible !== true ) fiber.evaluate(effect as any) if (fiber._exit) return fiber if (options?.signal) { if (options.signal.aborted) { fiber.interruptUnsafe() } else { const abort = () => fiber.interruptUnsafe() options.signal.addEventListener("abort", abort, { once: true }) fiber.addObserver(() => options.signal!.removeEventListener("abort", abort)) } } if (options?.onFiberStart) { options.onFiberStart(fiber) } return fiber } /** @internal */ export const fiberRunIn: { (scope: Scope.Scope): (self: Fiber.Fiber) => Fiber.Fiber ( self: Fiber.Fiber, scope: Scope.Scope ): Fiber.Fiber } = dual(2, ( self: FiberImpl, scope: Scope.Scope ): Fiber.Fiber => { if (self._exit) { return self } else if (scope.state._tag === "Closed") { self.interruptUnsafe(self.id) return self } scopeRemoveFinalizerUnsafe(scope, self) scopeAddFinalizerUnsafe(scope, self, fiberScopeFinalizer(self)) self.addObserver(() => scopeRemoveFinalizerUnsafe(scope, self)) return self }) /** @internal */ export const runFork: ( effect: Effect.Effect, options?: Effect.RunOptions | undefined ) => Fiber.Fiber = runForkWith(Context.empty()) /** @internal */ export const runCallbackWith = (context: Context.Context) => { const runFork = runForkWith(context) return ( effect: Effect.Effect, options?: | Effect.RunOptions & { readonly onExit: (exit: Exit.Exit) => void } | undefined ): (interruptor?: number | undefined) => void => { const fiber = runFork(effect, options) if (options?.onExit) { fiber.addObserver(options.onExit) } return (interruptor) => { return fiber.interruptUnsafe(interruptor) } } } /** @internal */ export const runCallback = runCallbackWith(Context.empty()) /** @internal */ export const runPromiseExitWith = (context: Context.Context) => { const runFork = runForkWith(context) return ( effect: Effect.Effect, options?: Effect.RunOptions | undefined ): Promise> => { const fiber = runFork(effect, options) return new Promise((resolve) => { fiber.addObserver((exit) => resolve(exit)) }) } } /** @internal */ export const runPromiseExit = runPromiseExitWith(Context.empty()) /** @internal */ export const runPromiseWith = (context: Context.Context) => { const runPromiseExit = runPromiseExitWith(context) return ( effect: Effect.Effect, options?: | Effect.RunOptions | undefined ): Promise => runPromiseExit(effect, options).then((exit) => { if (exit._tag === "Failure") { throw causeSquash(exit.cause) } return exit.value }) } /** @internal */ export const runPromise: ( effect: Effect.Effect, options?: | Effect.RunOptions | undefined ) => Promise = runPromiseWith(Context.empty()) /** @internal */ export const runSyncExitWith = (context: Context.Context) => { const runFork = runForkWith(context) return (effect: Effect.Effect): Exit.Exit => { if (effectIsExit(effect)) return effect const scheduler = new Scheduler.MixedScheduler("sync") const fiber = runFork(effect, { scheduler }) as FiberImpl fiber._dispatcher?.flush() return fiber._exit ?? exitDie(new AsyncFiberError(fiber)) } } /** @internal */ export const runSyncExit: (effect: Effect.Effect) => Exit.Exit = runSyncExitWith( Context.empty() ) /** @internal */ export const runSyncWith = (context: Context.Context) => { const runSyncExit = runSyncExitWith(context) return (effect: Effect.Effect): A => { const exit = runSyncExit(effect) if (exit._tag === "Failure") throw causeSquash(exit.cause) return exit.value } } /** @internal */ export const runSync: (effect: Effect.Effect) => A = runSyncWith(Context.empty()) const succeedTrue = succeed(true) const succeedFalse = succeed(false) class Latch implements _Latch.Latch { waiters: Array<(_: Effect.Effect) => void> = [] scheduled: Array<(_: Effect.Effect) => void> | undefined = undefined private _isOpen: boolean constructor(isOpen: boolean) { this._isOpen = isOpen } private scheduleUnsafe(fiber: Fiber.Fiber) { if (this.waiters.length === 0) { return succeedTrue } if (this.scheduled === undefined) { this.scheduled = this.waiters fiber.currentDispatcher.scheduleTask(this.flushScheduled, 0) } else { for (let i = 0; i < this.waiters.length; i++) { this.scheduled.push(this.waiters[i]) } } this.waiters = [] return succeedTrue } private flushScheduled = () => { if (this.scheduled === undefined) return const waiters = this.scheduled this.scheduled = undefined for (let i = 0; i < waiters.length; i++) { waiters[i](exitVoid) } } private flushWaiters() { // swap both arrays out before any resume runs: a resumed waiter can // reentrantly close the latch and register new waiters, which must not // be drained by this flush const waiters = this.waiters this.waiters = [] this.flushScheduled() for (let i = 0; i < waiters.length; i++) { waiters[i](exitVoid) } } open = withFiber((fiber) => { if (this._isOpen) return succeedFalse this._isOpen = true return this.scheduleUnsafe(fiber) }) release = withFiber((fiber) => this._isOpen ? succeedFalse : this.scheduleUnsafe(fiber)) openUnsafe() { if (this._isOpen) return false this._isOpen = true this.flushWaiters() return true } await = callback((resume) => { if (this._isOpen) { return resume(void_) } this.waiters.push(resume) return sync(() => { let index = this.waiters.indexOf(resume) if (index !== -1) { this.waiters.splice(index, 1) } else if (this.scheduled !== undefined) { index = this.scheduled.indexOf(resume) if (index !== -1) { this.scheduled.splice(index, 1) } } }) }) closeUnsafe() { if (!this._isOpen) return false this._isOpen = false return true } close = sync(() => this.closeUnsafe()) whenOpen = (self: Effect.Effect): Effect.Effect => flatMap(this.await, () => self) isOpen() { return this._isOpen } } /** @internal */ export const makeLatchUnsafe = (open?: boolean | undefined): _Latch.Latch => new Latch(open ?? false) /** @internal */ export const makeLatch = (open?: boolean | undefined) => sync(() => makeLatchUnsafe(open)) // ---------------------------------------------------------------------------- // Tracer // ---------------------------------------------------------------------------- /** @internal */ export const tracer: Effect.Effect = withFiber((fiber) => succeed(fiber.getRef(Tracer.Tracer))) /** @internal */ export const withTracer: { (tracer: Tracer.Tracer): (effect: Effect.Effect) => Effect.Effect (effect: Effect.Effect, tracer: Tracer.Tracer): Effect.Effect } = dual( 2, (effect: Effect.Effect, tracer: Tracer.Tracer): Effect.Effect => provideService(effect, Tracer.Tracer, tracer) ) /** @internal */ export const withTracerEnabled: { (enabled: boolean): (effect: Effect.Effect) => Effect.Effect (effect: Effect.Effect, enabled: boolean): Effect.Effect } = provideService(TracerEnabled) /** @internal */ export const withTracerTiming: { (enabled: boolean): (effect: Effect.Effect) => Effect.Effect (effect: Effect.Effect, enabled: boolean): Effect.Effect } = provideService(TracerTimingEnabled) const bigint0 = BigInt(0) const NoopSpanProto: Omit = { _tag: "Span", spanId: "noop", traceId: "noop", sampled: false, status: { _tag: "Ended", startTime: bigint0, endTime: bigint0, exit: exitVoid }, attributes: new Map(), links: [], kind: "internal", attribute() {}, event() {}, end() {}, addLinks() {} } /** @internal */ export const noopSpan = (options: { readonly name: string readonly parent: Option.Option readonly annotations: Context.Context }): Tracer.Span => Object.assign(Object.create(NoopSpanProto), options) const filterDisablePropagation = (span: Tracer.AnySpan | undefined): Option.Option => { if (!span) return Option.none() return Context.get(span.annotations, Tracer.DisablePropagation) ? span._tag === "Span" ? filterDisablePropagation(Option.getOrUndefined(span.parent)) : Option.none() : Option.some(span) } /** @internal */ export const makeSpanUnsafe = ( fiber: Fiber.Fiber, name: string, options: Tracer.SpanOptionsNoTrace | undefined ) => { const disablePropagation = !fiber.cache.tracerEnabled || (options?.annotations && Context.get(options.annotations, Tracer.DisablePropagation)) const parent = options?.parent !== undefined ? Option.some(options.parent) : options?.root ? Option.none() : filterDisablePropagation(fiber.cache.span) let span: Tracer.Span if (disablePropagation) { span = noopSpan({ name, parent, annotations: Context.add( options?.annotations ?? Context.empty(), Tracer.DisablePropagation, true ) }) } else { // The cache stores only explicit tracer overrides; absent references use the default. const tracer = fiber.cache.tracer ?? Tracer.nativeTracer const clock = fiber.getRef(ClockRef) const timingEnabled = fiber.getRef(TracerTimingEnabled) const annotationsFromEnv = fiber.getRef(TracerSpanAnnotations) const linksFromEnv = fiber.getRef(TracerSpanLinks) const level = options?.level ?? fiber.getRef(Tracer.CurrentTraceLevel) const links = options?.links !== undefined ? [...linksFromEnv, ...options.links] : linksFromEnv.length === 0 ? [] : linksFromEnv.slice() span = tracer.span({ name, parent, annotations: options?.annotations ?? Context.empty(), links, startTime: timingEnabled ? clock.currentTimeNanosUnsafe() : bigint0, kind: options?.kind ?? "internal", root: options?.root ?? false, sampled: options?.sampled ?? (Option.isSome(parent) && parent.value.sampled === false ? false : !isLogLevelGreaterThan(fiber.getRef(Tracer.MinimumTraceLevel), level)) }) for (const key in annotationsFromEnv) { span.attribute(key, annotationsFromEnv[key]) } if (options?.attributes !== undefined) { for (const key in options.attributes) { span.attribute(key, options.attributes[key]) } } } return span } /** @internal */ export const makeSpan = ( name: string, options?: Tracer.SpanOptions ): Effect.Effect => withFiber((fiber) => succeed(makeSpanUnsafe(fiber, name, options))) /** @internal */ export const makeSpanScoped = ( name: string, options?: Tracer.SpanOptionsNoTrace | undefined ): Effect.Effect => uninterruptible( withFiber((fiber) => { const scope = Context.getUnsafe(fiber.context, scopeTag) const span = makeSpanUnsafe(fiber, name, options ?? {}) const clock = fiber.getRef(ClockRef) const timingEnabled = fiber.getRef(TracerTimingEnabled) return as( scopeAddFinalizerExit(scope, (exit) => endSpan(span, exit, clock, timingEnabled)), span ) }) ) /** @internal */ export const withSpanScoped: { ( name: string, options?: Tracer.SpanOptions ): (self: Effect.Effect) => Effect.Effect | Scope.Scope> ( self: Effect.Effect, name: string, options?: Tracer.SpanOptions ): Effect.Effect | Scope.Scope> } = function() { const dataFirst = typeof arguments[0] !== "string" const name = dataFirst ? arguments[1] : arguments[0] const options = addSpanStackTrace(dataFirst ? arguments[2] : arguments[1]) if (dataFirst) { const self = arguments[0] return flatMap( makeSpanScoped(name, options), (span) => withParentSpan(self, span, options) ) } return (self: Effect.Effect) => flatMap( makeSpanScoped(name, options), (span) => withParentSpan(self, span, options) ) } as any const provideSpanStackFrame = (name: string, stack: (() => string | undefined) | undefined) => { stack = typeof stack === "function" ? stack : constUndefined return updateService(CurrentStackFrame, (parent) => ({ name, stack, parent })) } /** @internal */ export const spanAnnotations: Effect.Effect>> = TracerSpanAnnotations /** @internal */ export const spanLinks: Effect.Effect> = TracerSpanLinks /** @internal */ export const linkSpans: { ( span: Tracer.AnySpan | ReadonlyArray, attributes?: Record ): (self: Effect.Effect) => Effect.Effect ( self: Effect.Effect, span: Tracer.AnySpan | ReadonlyArray, attributes?: Record ): Effect.Effect } = dual((args) => isEffect(args[0]), ( self: Effect.Effect, span: Tracer.AnySpan | ReadonlyArray, attributes: Record = {} ): Effect.Effect => { const spans: Array = Array.isArray(span) ? span : [span] const links = spans.map((span): Tracer.SpanLink => ({ span, attributes })) return updateService(self, TracerSpanLinks, (current) => [...current, ...links]) }) /** @internal */ export const endSpan = ( span: Tracer.Span, exit: Exit.Exit, clock: Clock.Clock, timingEnabled: boolean ) => sync(() => { if (span.status._tag === "Ended") return span.end(timingEnabled ? clock.currentTimeNanosUnsafe() : bigint0, exit) }) /** @internal */ export const useSpan: { (name: string, evaluate: (span: Tracer.Span) => Effect.Effect): Effect.Effect ( name: string, options: Tracer.SpanOptionsNoTrace, evaluate: (span: Tracer.Span) => Effect.Effect ): Effect.Effect } = ( name: string, ...args: [evaluate: (span: Tracer.Span) => Effect.Effect] | [ options: any, evaluate: (span: Tracer.Span) => Effect.Effect ] ): Effect.Effect => { const options = args.length === 1 ? undefined : args[0] const evaluate: (span: Tracer.Span) => Effect.Effect = args[args.length - 1] return withFiber((fiber) => { const span = makeSpanUnsafe(fiber, name, options) const clock = fiber.getRef(ClockRef) const timingEnabled = fiber.getRef(TracerTimingEnabled) onExitUnsafe(fiber, (exit) => endSpan(span, exit, clock, timingEnabled)) return evaluate(span) }) } const provideParentSpan = provideService(Tracer.ParentSpan) /** @internal */ export const withParentSpan: { ( value: Tracer.AnySpan, options?: Tracer.TraceOptions ): (self: Effect.Effect) => Effect.Effect> ( self: Effect.Effect, value: Tracer.AnySpan, options?: Tracer.TraceOptions ): Effect.Effect> } = function() { const dataFirst = isEffect(arguments[0]) const span: Tracer.AnySpan = dataFirst ? arguments[1] : arguments[0] let options = dataFirst ? arguments[2] : arguments[1] let provideStackFrame: (self: Effect.Effect) => Effect.Effect = identity if (span._tag === "Span") { options = addSpanStackTrace(options) provideStackFrame = provideSpanStackFrame(span.name, options?.captureStackTrace) } if (dataFirst) { return provideParentSpan(provideStackFrame(arguments[0]), span) } return (self: Effect.Effect) => provideParentSpan(provideStackFrame(self), span) } as any /** @internal */ export const withSpan: { >( name: string, options?: Tracer.SpanOptionsNoTrace | ((...args: NoInfer) => Tracer.SpanOptionsNoTrace) | undefined, traceOptions?: Tracer.TraceOptions | undefined ): (self: Effect.Effect, ...args: Args) => Effect.Effect> ( self: Effect.Effect, name: string, options?: Tracer.SpanOptions | undefined ): Effect.Effect> } = function() { const dataFirst = typeof arguments[0] !== "string" const name = dataFirst ? arguments[1] : arguments[0] const traceOptions = addSpanStackTrace(arguments[2]) if (dataFirst) { const self = arguments[0] return useSpan(name, arguments[2], (span) => withParentSpan(self, span, traceOptions)) } const fnArg = typeof arguments[1] === "function" ? arguments[1] : undefined const options = fnArg ? undefined : arguments[1] return (self: Effect.Effect, ...args: any) => useSpan( name, fnArg ? fnArg(...args) : options, (span) => withParentSpan(self, span, traceOptions) ) } as any /** @internal */ export const annotateSpans: { (key: string, value: unknown): (effect: Effect.Effect) => Effect.Effect (values: Record): (effect: Effect.Effect) => Effect.Effect (effect: Effect.Effect, key: string, value: unknown): Effect.Effect (effect: Effect.Effect, values: Record): Effect.Effect } = dual( (args) => isEffect(args[0]), ( effect: Effect.Effect, ...args: [Record] | [key: string, value: unknown] ): Effect.Effect => updateService(effect, TracerSpanAnnotations, (annotations) => { const newAnnotations = args.length === 1 ? { ...annotations, ...args[0] } : { ...annotations } if (args.length === 1) { return newAnnotations } else { InternalRecord.assignProperty(newAnnotations, args[0], args[1]) } return newAnnotations }) ) /** @internal */ export const annotateCurrentSpan: { (key: string, value: unknown): Effect.Effect (values: Record): Effect.Effect } = (...args: [Record] | [key: string, value: unknown]) => withFiber((fiber) => { const span = fiber.currentSpanLocal if (span) { if (args.length === 1) { for (const [key, value] of Object.entries(args[0])) { span.attribute(key, value) } } else { span.attribute(args[0], args[1]) } } return void_ }) /** @internal */ export const currentSpan: Effect.Effect = withFiber((fiber) => { const span = fiber.currentSpanLocal return span ? succeed(span) : fail(new NoSuchElementError()) }) /** @internal */ export const currentParentSpan: Effect.Effect = serviceOptional( Tracer.ParentSpan ) // ---------------------------------------------------------------------------- // Clock // ---------------------------------------------------------------------------- /** @internal */ export const ClockRef = Context.Reference("effect/Clock", { defaultValue: (): Clock.Clock => new ClockImpl() }) const MAX_TIMER_MILLIS = 2 ** 31 - 1 class ClockImpl implements Clock.Clock { currentTimeMillisUnsafe(): number { return Date.now() } readonly currentTimeMillis: Effect.Effect = sync(() => this.currentTimeMillisUnsafe()) currentTimeNanosUnsafe(): bigint { return wallTimeNanos() } readonly currentTimeNanos: Effect.Effect = sync(() => this.currentTimeNanosUnsafe()) monotonicTimeNanosUnsafe(): bigint { return monotonicNowNanos() } readonly monotonicTimeNanos: Effect.Effect = sync(() => this.monotonicTimeNanosUnsafe()) sleep(duration: Duration.Duration): Effect.Effect { return this.sleepMillis(Duration.toMillis(duration)) } private sleepMillis(millis: number): Effect.Effect { if (millis <= 0) return yieldNow else if (!Number.isFinite(millis)) return never return callback((resume) => { const continuation = millis > MAX_TIMER_MILLIS ? this.sleepMillis(millis - MAX_TIMER_MILLIS) : void_ const handle = setTimeout(() => resume(continuation), Math.min(millis, MAX_TIMER_MILLIS)) return sync(() => clearTimeout(handle)) }) } } const nanosPerMilli = BigInt(1_000_000) const monotonicNowNanos = (function() { const processHrtime = (globalThis as { readonly process?: { readonly hrtime?: { readonly bigint?: () => bigint } } }).process?.hrtime if (typeof processHrtime?.bigint === "function") { return () => processHrtime.bigint!() } if (typeof performance !== "undefined" && typeof performance.now === "function") { return () => BigInt(Math.round(performance.now() * 1_000_000)) } let previous = BigInt(0) return () => { const current = BigInt(Date.now()) * nanosPerMilli if (current > previous) { previous = current } return previous } })() const wallTimeNanos = (function() { const reanchorThresholdNanos = BigInt(1_000_000_000) let origin: bigint | undefined return () => { const monotonic = monotonicNowNanos() const wall = BigInt(Date.now()) * nanosPerMilli if (origin === undefined) { origin = wall - monotonic } else { const projected = origin + monotonic const skew = wall > projected ? wall - projected : projected - wall if (skew > reanchorThresholdNanos) { origin = wall - monotonic } } return origin + monotonic } })() /** @internal */ export const clockWith = (f: (clock: Clock.Clock) => Effect.Effect): Effect.Effect => withFiber((fiber) => f(fiber.getRef(ClockRef))) /** @internal */ export const sleep = (duration: Duration.Input): Effect.Effect => clockWith((clock) => clock.sleep(Duration.fromInputUnsafe(duration))) /** @internal */ export const currentTimeMillis: Effect.Effect = clockWith((clock) => clock.currentTimeMillis) /** @internal */ export const currentTimeNanos: Effect.Effect = clockWith((clock) => clock.currentTimeNanos) /** @internal */ export const monotonicTimeNanos: Effect.Effect = clockWith((clock) => clock.monotonicTimeNanos) // ---------------------------------------------------------------------------- // Errors // ---------------------------------------------------------------------------- /** @internal */ export const TimeoutErrorTypeId = "~effect/Cause/TimeoutError" /** @internal */ export const isTimeoutError = (u: unknown): u is Cause.TimeoutError => hasProperty(u, TimeoutErrorTypeId) /** @internal */ export class TimeoutError extends TaggedError("TimeoutError") { readonly [TimeoutErrorTypeId] = TimeoutErrorTypeId constructor(message?: string) { super({ message } as any) } } /** @internal */ export const IllegalArgumentErrorTypeId = "~effect/Cause/IllegalArgumentError" /** @internal */ export const isIllegalArgumentError = ( u: unknown ): u is Cause.IllegalArgumentError => hasProperty(u, IllegalArgumentErrorTypeId) /** @internal */ export class IllegalArgumentError extends TaggedError("IllegalArgumentError") { readonly [IllegalArgumentErrorTypeId] = IllegalArgumentErrorTypeId constructor(message?: string) { super({ message } as any) } } /** @internal */ export const ExceededCapacityErrorTypeId = "~effect/Cause/ExceededCapacityError" /** @internal */ export const isExceededCapacityError = ( u: unknown ): u is Cause.ExceededCapacityError => hasProperty(u, ExceededCapacityErrorTypeId) /** @internal */ export class ExceededCapacityError extends TaggedError("ExceededCapacityError") { readonly [ExceededCapacityErrorTypeId] = ExceededCapacityErrorTypeId constructor(message?: string) { super({ message } as any) } } /** @internal */ export const AsyncFiberErrorTypeId = "~effect/Cause/AsyncFiberError" /** @internal */ export const isAsyncFiberError = ( u: unknown ): u is Cause.AsyncFiberError => hasProperty(u, AsyncFiberErrorTypeId) /** @internal */ export class AsyncFiberError extends TaggedError("AsyncFiberError")<{ fiber: Fiber.Fiber message: string }> { readonly [AsyncFiberErrorTypeId] = AsyncFiberErrorTypeId constructor(fiber: Fiber.Fiber) { super({ message: "An asynchronous Effect was executed with Effect.runSync", fiber }) } } /** @internal */ export const UnknownErrorTypeId = "~effect/Cause/UnknownError" /** @internal */ export const isUnknownError = ( u: unknown ): u is Cause.UnknownError => hasProperty(u, UnknownErrorTypeId) /** @internal */ export class UnknownError extends TaggedError("UnknownError")<{ cause: unknown message?: string | undefined }> { readonly [UnknownErrorTypeId] = UnknownErrorTypeId constructor(cause: unknown, message?: string) { super({ message, cause } as any) } } // ---------------------------------------------------------------------------- // Console // ---------------------------------------------------------------------------- /** @internal */ export const ConsoleRef = Context.Reference( "effect/Console", { defaultValue: (): Console.Console => globalThis.console } ) // ---------------------------------------------------------------------------- // LogLevel // ---------------------------------------------------------------------------- /** @internal */ export const logLevelToOrder = (level: LogLevel.LogLevel) => { switch (level) { case "All": return Number.MIN_SAFE_INTEGER case "Fatal": return 50_000 case "Error": return 40_000 case "Warn": return 30_000 case "Info": return 20_000 case "Debug": return 10_000 case "Trace": return 0 case "None": return Number.MAX_SAFE_INTEGER } } /** @internal */ export const LogLevelOrder = Order.mapInput(Order.Number, logLevelToOrder) /** @internal */ export const isLogLevelGreaterThan = Order.isGreaterThan(LogLevelOrder) // ---------------------------------------------------------------------------- // Logger // ---------------------------------------------------------------------------- /** @internal */ export const CurrentLoggers = Context.Reference< ReadonlySet> >("effect/Logger/CurrentLoggers", { defaultValue: () => new Set([defaultLogger, tracerLogger]) }) /** @internal */ export const LogToStderr = Context.Reference("effect/Logger/LogToStderr", { defaultValue: constFalse }) /** @internal */ export const annotateLogsScoped: { (key: string, value: unknown): Effect.Effect (values: Record): Effect.Effect } = function() { const entries = typeof arguments[0] === "string" ? [[arguments[0], arguments[1]]] : Object.entries(arguments[0]) return uninterruptible(withFiber((fiber) => { const prev = fiber.getRef(CurrentLogAnnotations) const next = { ...prev } for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i] InternalRecord.assignProperty(next, key, value) } fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next)) return scopeAddFinalizerExit(Context.getUnsafe(fiber.context, scopeTag), (_) => { const current = fiber.getRef(CurrentLogAnnotations) const next = { ...current } for (let i = 0; i < entries.length; i++) { const [key, value] = entries[i] if (current[key] !== value && !Object.is(current[key], value)) continue if (Object.hasOwn(prev, key)) { InternalRecord.assignProperty(next, key, prev[key]) } else { delete next[key] } } fiber.setContext(Context.add(fiber.context, CurrentLogAnnotations, next)) return void_ }) })) } /** @internal */ export const LoggerTypeId = "~effect/Logger" const LoggerProto = { [LoggerTypeId]: { _Message: identity, _Output: identity }, pipe() { return pipeArguments(this, arguments) } } /** @internal */ export const loggerMake = ( log: (options: Logger.Options) => Output ): Logger.Logger => { const self = Object.create(LoggerProto) self.log = log return self } /** * Sanitize a given string by replacing spaces, equal signs, and double quotes * with underscores. * * @internal */ export const formatLabel = (key: string) => key.replace(/[\s="]/g, "_") /** * Formats a log span into a `