UNPKG

@effect-ts/system

Version:

Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.

938 lines (848 loc) 27.9 kB
// ets_tracing: off import "../../../../Operator/index.js" import type * as Cause from "../../../../Cause/index.js" import * as L from "../../../../Collections/Immutable/List/index.js" import * as T from "../../../../Effect/index.js" import * as Either from "../../../../Either/index.js" import * as Exit from "../../../../Exit/index.js" import * as F from "../../../../Fiber/index.js" import * as O from "../../../../Option/index.js" import * as P from "./primitives.js" type ErasedExecutor<Env> = ChannelExecutor< Env, unknown, unknown, unknown, unknown, unknown, unknown > type ErasedChannel<R> = P.Channel< R, unknown, unknown, unknown, unknown, unknown, unknown > type ErasedContinuation<R> = P.Continuation< R, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown > type ErasedFinalizer<Env> = P.ContinuationFinalizer<Env, unknown, unknown> export type SubexecutorStack<R> = FromKAnd<R> | Inner<R> export const FromKAndTypeId = Symbol() export type FromKAndTypeId = typeof FromKAndTypeId export class FromKAnd<R> { readonly _typeId: FromKAndTypeId = FromKAndTypeId constructor(readonly fromK: ErasedExecutor<R>, readonly rest: Inner<R>) {} } export const InnerTypeId = Symbol() export type InnerTypeId = typeof InnerTypeId export class Inner<R> { readonly _typeId: InnerTypeId = InnerTypeId constructor( readonly exec: ErasedExecutor<R>, readonly subK: (u: unknown) => ErasedChannel<R>, readonly lastDone: unknown, readonly combineSubK: (a: unknown, b: unknown) => unknown, readonly combineSubKAndInner: (a: unknown, b: unknown) => unknown ) {} close( ex: Exit.Exit<unknown, unknown> ): T.RIO<R, Exit.Exit<unknown, unknown>> | undefined { const fin = this.exec.close(ex) if (fin) { return T.result(fin) } } } export const ChannelStateDoneTypeId = Symbol() export type ChannelStateDoneTypeId = typeof ChannelStateDoneTypeId export class ChannelStateDone { readonly _typeId: ChannelStateDoneTypeId = ChannelStateDoneTypeId } export const ChannelStateEmitTypeId = Symbol() export type ChannelStateEmitTypeId = typeof ChannelStateEmitTypeId export class ChannelStateEmit { readonly _typeId: ChannelStateEmitTypeId = ChannelStateEmitTypeId } export const ChannelStateEffectTypeId = Symbol() export type ChannelStateEffectTypeId = typeof ChannelStateEffectTypeId export class ChannelStateEffect<R, E> { readonly _typeId: ChannelStateEffectTypeId = ChannelStateEffectTypeId constructor(readonly effect: T.Effect<R, E, unknown>) {} } export type ChannelState<R, E> = | ChannelStateDone | ChannelStateEmit | ChannelStateEffect<R, E> const _ChannelStateDone = new ChannelStateDone() const _ChannelStateEmit = new ChannelStateEmit() export function channelStateEffect<R, E>( state: ChannelState<R, E> | undefined ): T.Effect<R, E, unknown> { if (state?._typeId === ChannelStateEffectTypeId) { return state.effect } return T.unit } export function channelStateUnroll<R, E>( runStep: () => ChannelState<R, E> ): T.Effect<R, E, Either.Either<ChannelStateEmit, ChannelStateDone>> { const step = runStep() switch (step._typeId) { case ChannelStateEffectTypeId: { return T.chain_(step.effect, () => channelStateUnroll(runStep)) } case ChannelStateDoneTypeId: { return T.succeed(Either.right(_ChannelStateDone)) } case ChannelStateEmitTypeId: { return T.succeed(Either.left(_ChannelStateEmit)) } } } export function maybeCloseBoth<Env>( l: T.Effect<Env, never, unknown> | undefined, r: T.Effect<Env, never, unknown> | undefined ): T.RIO<Env, Exit.Exit<never, unknown>> | undefined { if (l && r) { return T.zipWith_(T.result(l), T.result(r), (a, b) => Exit.zipRight_(a, b)) } else if (l) { return T.result(l) } else if (r) { return T.result(r) } } const endUnit = new P.Done(() => void 0) export class ChannelExecutor<Env, InErr, InElem, InDone, OutErr, OutElem, OutDone> { private input?: ErasedExecutor<Env> | undefined private inProgressFinalizer?: T.RIO<Env, Exit.Exit<unknown, unknown>> | undefined private subexecutorStack?: SubexecutorStack<Env> | undefined private doneStack: L.List<ErasedContinuation<Env>> = L.empty() private done?: Exit.Exit<unknown, unknown> | undefined private cancelled?: Exit.Exit<OutErr, OutDone> | undefined private emitted?: unknown | undefined private currentChannel?: ErasedChannel<Env> | undefined private closeLastSubstream?: T.Effect<Env, never, unknown> | undefined constructor( initialChannel: () => P.Channel< Env, InErr, InElem, InDone, OutErr, OutElem, OutDone >, private providedEnv: unknown, private executeCloseLastSubstream: ( io: T.Effect<Env, never, unknown> ) => T.Effect<Env, never, unknown> ) { this.currentChannel = initialChannel() as ErasedChannel<Env> } private restorePipe( exit: Exit.Exit<unknown, unknown>, prev: ErasedExecutor<Env> | undefined ): T.Effect<Env, never, unknown> | undefined { const currInput = this.input this.input = prev return currInput?.close(exit) } private unwindAllFinalizers( acc: Exit.Exit<unknown, unknown>, conts: L.List<ErasedContinuation<Env>>, exit: Exit.Exit<unknown, unknown> ): T.Effect<Env, unknown, unknown> { while (!L.isEmpty(conts)) { const head = L.unsafeFirst(conts)! P.concreteContinuation(head) if (head._typeId === P.ContinuationKTypeId) { conts = L.tail(conts) } else { return T.chain_(T.result(head.finalizer(exit)), (finExit) => this.unwindAllFinalizers(Exit.zipRight_(acc, finExit), L.tail(conts), exit) ) } } return T.done(acc) } private popAllFinalizers( exit: Exit.Exit<unknown, unknown> ): T.RIO<Env, Exit.Exit<unknown, unknown>> { const effect = T.result(this.unwindAllFinalizers(Exit.unit, this.doneStack, exit)) this.doneStack = L.empty() this.storeInProgressFinalizer(effect) return effect } private popNextFinalizersGo( stack: L.List<ErasedContinuation<Env>>, builder: L.MutableList<P.ContinuationFinalizer<Env, unknown, unknown>> ): L.List<ErasedContinuation<Env>> { while (!L.isEmpty(stack)) { const head = L.unsafeFirst(stack)! P.concreteContinuation(head) if (head._typeId === P.ContinuationKTypeId) { return stack } L.push_(builder, head) stack = L.tail(stack) } return L.empty() } private popNextFinalizers(): L.List<P.ContinuationFinalizer<Env, unknown, unknown>> { const builder = L.emptyPushable<P.ContinuationFinalizer<Env, unknown, unknown>>() this.doneStack = this.popNextFinalizersGo(this.doneStack, builder) return builder } private storeInProgressFinalizer( effect: T.RIO<Env, Exit.Exit<unknown, unknown>> | undefined ): void { this.inProgressFinalizer = effect } private clearInProgressFinalizer(): void { this.inProgressFinalizer = undefined } private ifNotNull<R, E>( effect: T.RIO<R, Exit.Exit<E, unknown>> | undefined ): T.RIO<R, Exit.Exit<E, unknown>> { return effect ? effect : T.succeed(Exit.unit) } close(ex: Exit.Exit<unknown, unknown>): T.Effect<Env, never, unknown> | undefined { const runInProgressFinalizer = this.inProgressFinalizer ? T.ensuring_( this.inProgressFinalizer, T.succeedWith(() => this.clearInProgressFinalizer()) ) : undefined let closeSubexecutors: T.RIO<Env, Exit.Exit<unknown, unknown>> | undefined if (this.subexecutorStack) { if (this.subexecutorStack._typeId === InnerTypeId) { closeSubexecutors = this.subexecutorStack.close(ex) } else { const fin1 = this.subexecutorStack.fromK.close(ex) const fin2 = this.subexecutorStack.rest.close(ex) if (fin1 && fin2) { closeSubexecutors = T.zipWith_(T.result(fin1), T.result(fin2), (a, b) => Exit.zipRight_(a, b) ) } else if (fin1) { closeSubexecutors = T.result(fin1) } else if (fin2) { closeSubexecutors = T.result(fin2) } } } let closeSelf: T.RIO<Env, Exit.Exit<unknown, unknown>> | undefined const selfFinalizers = this.popAllFinalizers(ex) if (selfFinalizers) { closeSelf = T.ensuring_( selfFinalizers, T.succeedWith(() => this.clearInProgressFinalizer()) ) } if (closeSubexecutors || runInProgressFinalizer || closeSelf) { return T.uninterruptible( T.map_( T.tuple( this.ifNotNull(closeSubexecutors), this.ifNotNull(runInProgressFinalizer), this.ifNotNull(closeSelf) ), ({ tuple: [a, b, c] }) => Exit.zipRight_(a, Exit.zipRight_(b, c)) ) ) } } getDone() { return this.done as Exit.Exit<OutErr, OutDone> } getEmit() { return this.emitted as OutElem } cancelWith(exit: Exit.Exit<OutErr, OutDone>) { this.cancelled = exit } run(): ChannelState<Env, OutErr> { let result: ChannelState<Env, unknown> | undefined = undefined while (!result) { if (this.cancelled) { result = this.processCancellation() } else if (this.subexecutorStack) { result = this.drainSubexecutor() } else { if (!this.currentChannel) { result = _ChannelStateDone } else { P.concrete(this.currentChannel) const currentChannel = this.currentChannel switch (currentChannel._typeId) { case P.BridgeTypeId: { this.currentChannel = currentChannel.channel if (this.input) { const inputExecutor = this.input this.input = undefined const drainer: T.RIO<Env, unknown> = T.zipRight_( currentChannel.input.awaitRead, T.suspend(() => { const state = inputExecutor.run() switch (state._typeId) { case ChannelStateEmitTypeId: { return T.chain_( currentChannel.input.emit(inputExecutor.getEmit()), () => drainer ) } case ChannelStateEffectTypeId: { return T.foldCauseM_( state.effect, (cause) => currentChannel.input.error(cause), () => drainer ) } case ChannelStateDoneTypeId: { const done = inputExecutor.getDone() return done._tag === "Success" ? currentChannel.input.done(done.value) : currentChannel.input.error(done.cause) } } }) ) result = new ChannelStateEffect( T.chain_(T.fork(drainer), (fiber) => T.succeedWith(() => { this.addFinalizer( new P.ContinuationFinalizer((exit) => T.chain_(F.interrupt(fiber), () => T.suspend( () => this.restorePipe(exit, inputExecutor) || T.unit ) ) ) ) }) ) ) } break } case P.PipeToTypeId: { const previousInput = this.input const leftExec = new ChannelExecutor( currentChannel.left, this.providedEnv, this.executeCloseLastSubstream ) leftExec.input = previousInput this.input = leftExec this.addFinalizer( new P.ContinuationFinalizer( (exit) => this.restorePipe(exit, previousInput) || T.unit ) ) this.currentChannel = currentChannel.right() break } case P.ReadTypeId: { result = this.runRead(currentChannel) break } case P.DoneTypeId: { result = this.doneSucceed(currentChannel.terminal()) break } case P.HaltTypeId: { result = this.doneHalt(currentChannel.error()) break } case P.EffectTypeId: { const peffect = typeof this.providedEnv !== "undefined" ? T.provideAll_(currentChannel.effect, this.providedEnv as Env) : currentChannel.effect result = new ChannelStateEffect( T.foldCauseM_( peffect, (cause) => { const res = this.doneHalt(cause) if (res?._typeId === ChannelStateEffectTypeId) { return res.effect } else { return T.unit } }, (z) => { const res = this.doneSucceed(z) if (res?._typeId === ChannelStateEffectTypeId) { return res.effect } else { return T.unit } } ) ) break } case P.EmitTypeId: { this.emitted = currentChannel.out() this.currentChannel = endUnit result = _ChannelStateEmit break } case P.EnsuringTypeId: { this.addFinalizer( new P.ContinuationFinalizer((e) => currentChannel.finalizer(e)) ) this.currentChannel = currentChannel.channel break } case P.ConcatAllTypeId: { const innerExecuteLastClose = (f: T.Effect<Env, never, unknown>) => T.succeedWith(() => { const prevLastClose = this.closeLastSubstream ? this.closeLastSubstream : T.unit this.closeLastSubstream = T.zipRight_(prevLastClose, f) }) const exec = new ChannelExecutor( () => currentChannel.value, this.providedEnv, innerExecuteLastClose ) exec.input = this.input this.subexecutorStack = new Inner( exec, currentChannel.k, undefined, currentChannel.combineInners, currentChannel.combineAll ) this.closeLastSubstream = undefined this.currentChannel = undefined break } case P.FoldTypeId: { this.doneStack = L.prepend_(this.doneStack, currentChannel.k) this.currentChannel = currentChannel.value break } case P.BracketOutTypeId: { result = this.runBracketOut(currentChannel) break } case P.ProvideTypeId: { const previousEnv = this.providedEnv this.providedEnv = currentChannel.env this.currentChannel = currentChannel.channel this.addFinalizer( new P.ContinuationFinalizer(() => T.succeedWith(() => { this.providedEnv = previousEnv }) ) ) break } case P.EffectTotalTypeId: { result = this.doneSucceed(currentChannel.effect()) break } case P.EffectSuspendTotalTypeId: { this.currentChannel = currentChannel.effect() break } } } } } return result! as ChannelState<Env, OutErr> } private runReadGo( state: ChannelState<Env, unknown>, read: P.Read< Env, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown >, input: ErasedExecutor<Env> ): T.RIO<Env, void> { switch (state._typeId) { case ChannelStateEmitTypeId: { return T.succeedWith(() => { this.currentChannel = read.more(input.getEmit()) }) } case ChannelStateDoneTypeId: { return T.succeedWith(() => { this.currentChannel = read.done.onExit(input.getDone()) }) } case ChannelStateEffectTypeId: { return T.foldCauseM_( state.effect, (cause) => T.succeedWith(() => { this.currentChannel = read.done.onHalt(cause) }), () => this.runReadGo(input.run(), read, input) ) } } } private runRead( read: P.Read< Env, unknown, unknown, unknown, unknown, unknown, unknown, unknown, unknown > ): ChannelState<Env, unknown> | undefined { if (this.input) { const input = this.input const state = input.run() switch (state._typeId) { case ChannelStateEmitTypeId: { this.currentChannel = read.more(input.getEmit()) return } case ChannelStateDoneTypeId: { this.currentChannel = read.done.onExit(input.getDone()) return } case ChannelStateEffectTypeId: { return new ChannelStateEffect( T.foldCauseM_( state.effect, (cause) => T.succeedWith(() => { this.currentChannel = read.done.onHalt(cause) }), () => this.runReadGo(input.run(), read, input) ) ) } } } else { this.currentChannel = read.more(void 0) } } private runBracketOut( bracketOut: P.BracketOut<Env, unknown, unknown, unknown> ): ChannelState<Env, unknown> | undefined { return new ChannelStateEffect( T.uninterruptibleMask((mask) => T.foldCauseM_( mask.restore(bracketOut.acquire), (cause) => T.succeedWith(() => { this.currentChannel = new P.Halt(() => cause) }), (out) => T.succeedWith(() => { this.addFinalizer( new P.ContinuationFinalizer((e) => bracketOut.finalizer(out, e)) ) this.currentChannel = new P.Emit(() => out) }) ) ) ) } private addFinalizer(f: ErasedFinalizer<Env>) { this.doneStack = L.prepend_(this.doneStack, f) } private drainSubexecutor(): ChannelState<Env, unknown> | undefined { const subexecutorStack = this.subexecutorStack! if (subexecutorStack._typeId === InnerTypeId) { return this.drainInnerSubExecutor(subexecutorStack) } else { return this.drainFromKAndSubexecutor( subexecutorStack.fromK, subexecutorStack.rest ) } } private handleSubexecFailure<Env, InErr, InElem, InDone, OutErr, OutElem, OutDone>( exec: ErasedExecutor<Env>, rest: Inner<Env>, self: ChannelExecutor<Env, InErr, InElem, InDone, OutErr, OutElem, OutDone>, cause: Cause.Cause<unknown> ): ChannelState<Env, unknown> | undefined { return self.finishSubexecutorWithCloseEffect( Exit.halt(cause), (_) => rest.exec.close(_), (_) => exec.close(_) ) } private drainFromKAndSubexecutor( exec: ErasedExecutor<Env>, rest: Inner<Env> ): ChannelState<Env, unknown> | undefined { const run = exec.run() switch (run._typeId) { case ChannelStateEffectTypeId: { return new ChannelStateEffect( T.catchAllCause_(run.effect, (cause) => channelStateEffect(this.handleSubexecFailure(exec, rest, this, cause)) ) ) } case ChannelStateEmitTypeId: { this.emitted = exec.getEmit() return _ChannelStateEmit } case ChannelStateDoneTypeId: { const done = exec.getDone() switch (done._tag) { case "Failure": { return this.handleSubexecFailure(exec, rest, this, done.cause) } case "Success": { const modifiedRest = new Inner( rest.exec, rest.subK, rest.lastDone ? rest.combineSubK(rest.lastDone, done.value) : done.value, rest.combineSubK, rest.combineSubKAndInner ) this.closeLastSubstream = exec.close(done) this.replaceSubexecutor(modifiedRest) return undefined } } } } } private replaceSubexecutor(nextSubExec: Inner<Env>) { this.currentChannel = undefined this.subexecutorStack = nextSubExec } private finishSubexecutorWithCloseEffect( subexecDone: Exit.Exit<unknown, unknown>, ...closeFns: (( ex: Exit.Exit<unknown, unknown> ) => T.Effect<Env, never, unknown> | undefined)[] ): ChannelState<Env, unknown> | undefined { this.addFinalizer( new P.ContinuationFinalizer((_) => T.forEachUnit_(closeFns, (closeFn) => T.chain_( T.succeedWith(() => closeFn(subexecDone)), (closeEffect) => { if (closeEffect) { return closeEffect } else { return T.unit } } ) ) ) ) const state = Exit.fold_( subexecDone, (e) => this.doneHalt(e), (a) => this.doneSucceed(a) ) this.subexecutorStack = undefined return state } private doneSucceed(z: unknown): ChannelState<Env, unknown> | undefined { if (L.isEmpty(this.doneStack)) { this.done = Exit.succeed(z) this.currentChannel = undefined return _ChannelStateDone } const head = L.unsafeFirst(this.doneStack)! P.concreteContinuation(head) if (head._typeId === P.ContinuationKTypeId) { this.doneStack = L.tail(this.doneStack) this.currentChannel = head.onSuccess(z) return } else { const finalizers = this.popNextFinalizers() if (L.isEmpty(this.doneStack)) { this.doneStack = finalizers this.done = Exit.succeed(z) this.currentChannel = undefined return _ChannelStateDone } else { const finalizerEffect = this.runFinalizers( L.map_(finalizers, (_) => _.finalizer), Exit.succeed(z) ) this.storeInProgressFinalizer(finalizerEffect) return new ChannelStateEffect( T.chain_( T.uninterruptible( T.ensuring_( finalizerEffect, T.succeedWith(() => { this.clearInProgressFinalizer() }) ) ), () => T.succeedWith(() => this.doneSucceed(z)) ) ) } } } private runFinalizers( finalizers: L.List<(e: Exit.Exit<unknown, unknown>) => T.RIO<Env, unknown>>, ex: Exit.Exit<unknown, unknown> ): T.RIO<Env, Exit.Exit<unknown, unknown>> { if (L.isEmpty(finalizers)) { return T.succeed(Exit.unit) } return T.map_( T.forEach_(finalizers, (cont) => T.result(cont(ex))), (results) => O.getOrElse_(Exit.collectAll(...results), () => Exit.unit) ) } private doneHalt( cause: Cause.Cause<unknown> ): ChannelState<Env, unknown> | undefined { if (L.isEmpty(this.doneStack)) { this.done = Exit.halt(cause) this.currentChannel = undefined return _ChannelStateDone } const head = L.unsafeFirst(this.doneStack)! P.concreteContinuation(head) if (head._typeId === P.ContinuationKTypeId) { this.doneStack = L.tail(this.doneStack) this.currentChannel = head.onHalt(cause) return } else { const finalizers = this.popNextFinalizers() if (L.isEmpty(this.doneStack)) { this.doneStack = finalizers this.done = Exit.halt(cause) this.currentChannel = undefined return _ChannelStateDone } else { const finalizerEffect = this.runFinalizers( L.map_(finalizers, (_) => _.finalizer), Exit.halt(cause) ) this.storeInProgressFinalizer(finalizerEffect) return new ChannelStateEffect( T.chain_( T.uninterruptible( T.ensuring_( finalizerEffect, T.succeedWith(() => { this.clearInProgressFinalizer() }) ) ), () => T.succeedWith(() => this.doneHalt(cause)) ) ) } } } private drainInnerSubExecutor( inner: Inner<Env> ): ChannelState<Env, unknown> | undefined { const run = inner.exec.run() switch (run._typeId) { case ChannelStateEmitTypeId: { if (this.closeLastSubstream) { const closeLast = this.closeLastSubstream this.closeLastSubstream = undefined return new ChannelStateEffect( T.map_(this.executeCloseLastSubstream(closeLast), (_) => { const fromK: ErasedExecutor<Env> = new ChannelExecutor( () => inner.subK(inner.exec.getEmit()), this.providedEnv, this.executeCloseLastSubstream ) fromK.input = this.input this.subexecutorStack = new FromKAnd(fromK, inner) }) ) } else { const fromK: ErasedExecutor<Env> = new ChannelExecutor( () => inner.subK(inner.exec.getEmit()), this.providedEnv, this.executeCloseLastSubstream ) fromK.input = this.input this.subexecutorStack = new FromKAnd(fromK, inner) return undefined } } case ChannelStateDoneTypeId: { const lastClose = this.closeLastSubstream const done = inner.exec.getDone() switch (done._tag) { case "Failure": { return this.finishSubexecutorWithCloseEffect( done, () => lastClose, (_) => inner.exec.close(_) ) } case "Success": { const doneValue = Exit.succeed( inner.combineSubKAndInner(inner.lastDone, done.value) ) return this.finishSubexecutorWithCloseEffect( doneValue, () => lastClose, (_) => inner.exec.close(_) ) } } } case ChannelStateEffectTypeId: { const closeLast = this.closeLastSubstream ? this.closeLastSubstream : T.unit this.closeLastSubstream = undefined return new ChannelStateEffect( T.zipRight_( this.executeCloseLastSubstream(closeLast), T.catchAllCause_(run.effect, (cause) => channelStateEffect( this.finishSubexecutorWithCloseEffect( Exit.halt(cause), (_) => inner.exec.close(_), (_) => inner.exec.close(_) ) ) ) ) ) } } } private processCancellation(): ChannelState<Env, unknown> { this.currentChannel = undefined this.done = this.cancelled this.cancelled = undefined return _ChannelStateDone } }