UNPKG

@effect-ts/system

Version:

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

746 lines (679 loc) 22.2 kB
// ets_tracing: off import type * as C from "../Cause/index.js" import { reduce as chunkReduce } from "../Collections/Immutable/Chunk/api/reduce.js" import { insert } from "../Collections/Immutable/Map/index.js" import * as Tp from "../Collections/Immutable/Tuple/index.js" import { _E, _RIn, _ROut } from "../Effect/commons.js" import { sequential } from "../Effect/ExecutionStrategy.js" import type { Exit } from "../Exit/index.js" import { pipe } from "../Function/index.js" import { chain as managedChain, chain_ as managedChain_, foldCauseM_ as managedFoldCauseM_, map as managedMap, map_ as managedMap_, provideAll_ as managedProvideAll_, provideSome_ as managedProvideSome_, zipWith_ as managedZipWith_, zipWithPar_ as managedZipWithPar_ } from "../Managed/core.js" import { bind as managedBind, do as managedDo } from "../Managed/do.js" import { forEach_ as managedForEach_, forEachPar_ as managedForEachPar_ } from "../Managed/forEach.js" import { fromEffect as managedFromEffect } from "../Managed/fromEffect.js" import { managedApply } from "../Managed/managed.js" import { environment as managedEnvironment } from "../Managed/methods/environment.js" import * as add from "../Managed/ReleaseMap/add.js" import * as Finalizer from "../Managed/ReleaseMap/finalizer.js" import type { ReleaseMap } from "../Managed/ReleaseMap/index.js" import * as makeReleaseMap from "../Managed/ReleaseMap/makeReleaseMap.js" import * as releaseAll from "../Managed/ReleaseMap/releaseAll.js" import { succeed as succeed_1 } from "../Managed/succeed.js" import { use_ } from "../Managed/use.js" import { await as promiseAwait } from "../Promise/await.js" import { halt } from "../Promise/halt.js" import { make } from "../Promise/make.js" import { succeed } from "../Promise/succeed.js" import * as R from "../Ref/index.js" import * as RM from "../RefM/index.js" import { AtomicReference } from "../Support/AtomicReference/index.js" import type { Erase, UnionToIntersection } from "../Utils/index.js" import * as T from "./deps-effect.js" import type { Managed } from "./deps-managed.js" /** * Creates a layer from an effect */ export function fromRawEffect<R, E, A>(resource: T.Effect<R, E, A>): Layer<R, E, A> { return new LayerManaged(managedFromEffect(resource)) } /** * Creates a layer from a function */ export function fromRawFunction<A, B>(f: (a: A) => B) { return fromRawEffect(T.access(f)) } /** * Creates a layer from an effectful function */ export function fromRawFunctionM<A, R, E, B>(f: (a: A) => T.Effect<R, E, B>) { return fromRawEffect(T.accessM(f)) } /** * Creates a layer from a managed environment */ export function fromRawManaged<R, E, A>(resource: Managed<R, E, A>): Layer<R, E, A> { return new LayerManaged(resource) } /** * Constructs a layer that passes along the specified environment as an * output. */ export function identity<R>() { return fromRawManaged(managedEnvironment<R>()) } /** * Merge two Layers in parallel without providing any data to each other * * @param self - first Layer to combine * @param that - second Layer to combine */ export function and_<R, E, A, R2, E2, A2>( self: Layer<R, E, A>, that: Layer<R2, E2, A2> ): Layer<R & R2, E | E2, A & A2> { return new LayerZipWithPar(self, that, (l, r) => ({ ...l, ...r })) } /** * Merge two Layers in parallel without providing any data to each other * * @param that - second Layer to combine * @param self - first Layer to combine */ export function and<R2, E2, A2>( that: Layer<R2, E2, A2> ): <R, E, A>(self: Layer<R, E, A>) => Layer<R & R2, E | E2, A & A2> { return (self) => new LayerZipWithPar(self, that, (l, r) => ({ ...l, ...r })) } /** * Feeds the error or output services of this layer into the input of either * the specified `failure` or `success` layers, resulting in a new layer with * the inputs of this layer, and the error or outputs of the specified layer. */ export function fold<R, E, A>(self: Layer<R, E, A>) { return <R2, E2, A2>(failure: Layer<Tp.Tuple<[R2, C.Cause<E>]>, E2, A2>) => <E3, A3>(success: Layer<A, E3, A3>): Layer<R & R2, E3 | E2, A3 | A2> => new LayerFold<R, E, A, R2, E2, A2, E3, A3>(self, failure, success) } /** * Use `from` to partially provide environment into `to` */ export function using<R2, E2, A2>(from: Layer<R2, E2, A2>) { return <R, E, A>(to: Layer<A2 & R, E, A>): Layer<R2 & R, E2 | E, A> => compose_(from["+++"](identity<R>()), to) } /** * Use `from` to partially provide environment into `to` and merge both */ export function usingAnd<R2, E2, A2>(from: Layer<R2, E2, A2>) { return <R, E, A>(to: Layer<A2 & R, E, A>): Layer<R2 & R, E2 | E, A & A2> => compose_(from["+++"](identity<R>()), to["+++"](identity<A2>())) } /** * Compose layers */ export function compose_<R2, E2, A2, E, A>( from: Layer<R2, E2, A2>, to: Layer<A2, E, A> ): Layer<R2, E2 | E, A> { return fold(from)( fromRawFunctionM((_: Tp.Tuple<[unknown, C.Cause<E2>]>) => T.halt(_.get(1))) )(to) } /** * Compose layers */ export function compose<A2, E, A>( to: Layer<A2, E, A> ): <R2, E2>(from: Layer<R2, E2, A2>) => Layer<R2, E2 | E, A> { return (from) => compose_(from, to) } export const hashSym: unique symbol = Symbol() export abstract class Layer<RIn, E, ROut> { readonly [hashSym] = new AtomicReference<PropertyKey>(Symbol()); readonly [_RIn]!: (_: RIn) => void; readonly [_E]!: () => E; readonly [_ROut]!: () => ROut /** * Set the hash key for memoization */ setKey(hash: PropertyKey) { this[hashSym].set(hash) return this } ["_I"](): LayerInstruction { return this as any } /** * Use that Layer to provide data to this */ ["<=<"]<R2, E2>(that: Layer<R2, E2, RIn>): Layer<R2, E2 | E, ROut> { return that[">=>"](this) } /** * Use this Layer to provide data to that */ [">=>"]<E2, A2>(that: Layer<ROut, E2, A2>): Layer<RIn, E2 | E, A2> { return compose_(this, that) } /** * Use that Layer to partially provide data to this */ ["<<<"]<R2, E2, A2>( that: Layer<R2, E2, A2> ): Layer<Erase<RIn, A2> & R2, E2 | E, ROut> { return that[">>>"](this) } /** * Use this Layer to partially provide data to that */ [">>>"]<R2, E2, A2>(that: Layer<R2, E2, A2>): Layer<Erase<R2, ROut> & RIn, E2 | E, A2> [">>>"]<R2, E2, A2>(that: Layer<R2 & ROut, E2, A2>): Layer<R2 & RIn, E2 | E, A2> { return this["+++"](identity<R2>())[">=>"](that) } /** * Create a Layer with the data from both Layers, while providing the data from this to that */ [">+>"]<R2, E2, A2>( that: Layer<R2, E2, A2> ): Layer<RIn & Erase<ROut & R2, ROut>, E2 | E, ROut & A2> { return this[">>>"](that["+++"](identity<ROut>())) } /** * Create a Layer with the data from both Layers, while providing the data from that to this */ ["<+<"]<R2, E2, A2>( that: Layer<R2, E2, A2> ): Layer<Erase<RIn & A2, A2> & R2, E | E2, ROut & A2> { return that[">+>"](this) } /** * Combine both layers in parallel */ ["+++"]<R2, E2, A2>(from: Layer<R2, E2, A2>): Layer<R2 & RIn, E2 | E, ROut & A2> { return and_(from, this) } /** * Use the layer to provide partial environment to an effect */ use<R, E1, A>(effect: T.Effect<R & ROut, E1, A>): T.Effect<RIn & R, E | E1, A> { return provideSomeLayer(this)(effect) } /** * Use the layer to provide the full environment to an effect */ useAll<E1, A>(effect: T.Effect<ROut, E1, A>): T.Effect<RIn, E | E1, A> { return provideLayer(this)(effect) } /** * Use the layer to provide the full environment to an effect */ get useForever(): T.Effect<RIn, E, never> { return provideLayer(this)(T.never) } } /** * Provides a layer to the given effect */ export function provideSomeLayer<R, E, A>(layer: Layer<R, E, A>) { return <R1, E1, A1>(self: T.Effect<R1 & A, E1, A1>): T.Effect<R & R1, E | E1, A1> => provideLayer_(self, layer["+++"](identity())) } /** * Provides a layer to the given effect */ export function provideSomeLayer_<R1, E1, A1, R, E, A>( self: T.Effect<R1 & A, E1, A1>, layer: Layer<R, E, A> ): T.Effect<R & R1, E | E1, A1> { return provideLayer_(self, layer["+++"](identity())) } /** * Provides a layer to the given effect */ export function provideLayer_<R, E, A, E1, A1>( self: T.Effect<A, E1, A1>, layer: Layer<R, E, A> ): T.Effect<R, E | E1, A1> { return use_(build(layer), (p) => T.provideAll_(self, p)) } /** * Provides a layer to the given effect */ export function provideLayer<R, E, A>(layer: Layer<R, E, A>) { return <E1, A1>(self: T.Effect<A, E1, A1>) => provideLayer_(self, layer) } export type LayerInstruction = | LayerFold<any, any, any, any, any, any, any, any> | LayerFresh<any, any, any> | LayerManaged<any, any, any> | LayerSuspend<any, any, any> | LayerZipWithPar<any, any, any, any, any, any, any> | LayerZipWithSeq<any, any, any, any, any, any, any> | LayerAllPar<any> | LayerAllSeq<any> | LayerMap<any, any, any, any> | LayerChain<any, any, any, any, any, any> export class LayerFold<R, E, A, R2, E2, A2, E3, A3> extends Layer< R & R2, E2 | E3, A2 | A3 > { readonly _tag = "LayerFold" constructor( readonly self: Layer<R, E, A>, readonly failure: Layer<Tp.Tuple<[R2, C.Cause<E>]>, E2, A2>, readonly success: Layer<A, E3, A3> ) { super() } } export class LayerMap<RIn, E, ROut, ROut1> extends Layer<RIn, E, ROut1> { readonly _tag = "LayerMap" constructor(readonly self: Layer<RIn, E, ROut>, readonly f: (a: ROut) => ROut1) { super() } } export class LayerChain<RIn, RIn2, E, E2, ROut, ROut1> extends Layer< RIn & RIn2, E | E2, ROut1 > { readonly _tag = "LayerChain" constructor( readonly self: Layer<RIn, E, ROut>, readonly f: (a: ROut) => Layer<RIn2, E2, ROut1> ) { super() } } export class LayerFresh<RIn, E, ROut> extends Layer<RIn, E, ROut> { readonly _tag = "LayerFresh" constructor(readonly self: Layer<RIn, E, ROut>) { super() } } export class LayerManaged<RIn, E, ROut> extends Layer<RIn, E, ROut> { readonly _tag = "LayerManaged" constructor(readonly self: Managed<RIn, E, ROut>) { super() } } export class LayerSuspend<RIn, E, ROut> extends Layer<RIn, E, ROut> { readonly _tag = "LayerSuspend" constructor(readonly self: () => Layer<RIn, E, ROut>) { super() } } export class LayerZipWithPar<RIn, E, ROut, RIn1, E1, ROut2, ROut3> extends Layer< RIn & RIn1, E | E1, ROut3 > { readonly _tag = "LayerZipWithPar" constructor( readonly self: Layer<RIn, E, ROut>, readonly that: Layer<RIn1, E1, ROut2>, readonly f: (s: ROut, t: ROut2) => ROut3 ) { super() } } export type MergeR<Ls extends Layer<any, any, any>[]> = UnionToIntersection< { [k in keyof Ls]: [Ls[k]] extends [Layer<infer X, any, any>] ? unknown extends X ? never : X : never }[number] > export type MergeE<Ls extends Layer<any, any, any>[]> = { [k in keyof Ls]: [Ls[k]] extends [Layer<any, infer X, any>] ? X : never }[number] export type MergeA<Ls extends Layer<any, any, any>[]> = UnionToIntersection< { [k in keyof Ls]: [Ls[k]] extends [Layer<any, any, infer X>] ? unknown extends X ? never : X : never }[number] > export class LayerAllPar<Layers extends Layer<any, any, any>[]> extends Layer< MergeR<Layers>, MergeE<Layers>, MergeA<Layers> > { readonly _tag = "LayerAllPar" constructor(readonly layers: Layers & { 0: Layer<any, any, any> }) { super() } } export class LayerAllSeq<Layers extends Layer<any, any, any>[]> extends Layer< MergeR<Layers>, MergeE<Layers>, MergeA<Layers> > { readonly _tag = "LayerAllSeq" constructor(readonly layers: Layers & { 0: Layer<any, any, any> }) { super() } } export class LayerZipWithSeq<RIn, E, ROut, RIn1, E1, ROut2, ROut3> extends Layer< RIn & RIn1, E | E1, ROut3 > { readonly _tag = "LayerZipWithSeq" constructor( readonly self: Layer<RIn, E, ROut>, readonly that: Layer<RIn1, E1, ROut2>, readonly f: (s: ROut, t: ROut2) => ROut3 ) { super() } } export function scope<R, E, A>( _: Layer<R, E, A> ): Managed<unknown, never, (_: MemoMap) => Managed<R, E, A>> { const I = _._I() switch (I._tag) { case "LayerFresh": { return succeed_1(() => build(I.self)) } case "LayerManaged": { return succeed_1(() => I.self) } case "LayerSuspend": { return succeed_1((memo) => memo.getOrElseMemoize(I.self())) } case "LayerMap": { return succeed_1((memo) => managedMap_(memo.getOrElseMemoize(I.self), I.f)) } case "LayerChain": { return succeed_1((memo) => managedChain_(memo.getOrElseMemoize(I.self), (a) => memo.getOrElseMemoize(I.f(a)) ) ) } case "LayerZipWithPar": { return succeed_1((memo) => managedZipWithPar_( memo.getOrElseMemoize(I.self), memo.getOrElseMemoize(I.that), I.f ) ) } case "LayerZipWithSeq": { return succeed_1((memo) => managedZipWith_( memo.getOrElseMemoize(I.self), memo.getOrElseMemoize(I.that), I.f ) ) } case "LayerAllPar": { return succeed_1((memo) => { return pipe( managedForEachPar_(I.layers as Layer<any, any, any>[], memo.getOrElseMemoize), managedMap(chunkReduce({} as any, (b, a) => ({ ...b, ...a }))) ) }) } case "LayerAllSeq": { return succeed_1((memo) => { return pipe( managedForEach_(I.layers as Layer<any, any, any>[], memo.getOrElseMemoize), managedMap(chunkReduce({} as any, (b, a) => ({ ...b, ...a }))) ) }) } case "LayerFold": { return succeed_1((memo) => managedFoldCauseM_( memo.getOrElseMemoize(I.self), (e) => pipe( managedFromEffect(T.environment<any>()), managedChain((r) => managedProvideSome_(memo.getOrElseMemoize(I.failure), () => Tp.tuple(r, e) ) ) ), (r) => managedProvideAll_(memo.getOrElseMemoize(I.success), r) ) ) } } } /** * Builds a layer into a managed value. */ export function build<R, E, A>(_: Layer<R, E, A>): Managed<R, E, A> { return pipe( managedDo, managedBind("memoMap", () => managedFromEffect(makeMemoMap())), managedBind("run", () => scope(_)), managedBind("value", ({ memoMap, run }) => run(memoMap)), managedMap(({ value }) => value) ) } /** * Creates a MemoMap */ export function makeMemoMap() { return pipe( RM.makeRefM< ReadonlyMap<PropertyKey, Tp.Tuple<[T.IO<any, any>, Finalizer.Finalizer]>> >(new Map()), T.chain((r) => T.succeedWith(() => new MemoMap(r))) ) } /** * A `MemoMap` memoizes dependencies. */ export class MemoMap { constructor( readonly ref: RM.RefM< ReadonlyMap<PropertyKey, Tp.Tuple<[T.IO<any, any>, Finalizer.Finalizer]>> > ) {} /** * Checks the memo map to see if a dependency exists. If it is, immediately * returns it. Otherwise, obtains the dependency, stores it in the memo map, * and adds a finalizer to the outer `Managed`. */ getOrElseMemoize = <R, E, A>(layer: Layer<R, E, A>) => { return managedApply<R, E, A>( pipe( this.ref, RM.modify((m) => { const inMap = m.get(layer[hashSym].get) if (inMap) { const { tuple: [acquire, release] } = inMap const cached = T.accessM(({ tuple: [_, rm] }: Tp.Tuple<[R, ReleaseMap]>) => pipe( acquire as T.IO<E, A>, T.onExit((ex) => { switch (ex._tag) { case "Success": { return add.add(release)(rm) } case "Failure": { return T.unit } } }), T.map((x) => Tp.tuple(release, x)) ) ) return T.succeed(Tp.tuple(cached, m)) } else { return pipe( T.do, T.bind("observers", () => R.makeRef(0)), T.bind("promise", () => make<E, A>()), T.bind("finalizerRef", () => R.makeRef<Finalizer.Finalizer>(Finalizer.noopFinalizer) ), T.let("resource", ({ finalizerRef, observers, promise }) => T.uninterruptibleMask(({ restore }) => pipe( T.do, T.bind("env", () => T.environment<Tp.Tuple<[R, ReleaseMap]>>()), T.let( "a", ({ env: { tuple: [a] } }) => a ), T.let( "outerReleaseMap", ({ env: { tuple: [_, outerReleaseMap] } }) => outerReleaseMap ), T.bind("innerReleaseMap", () => makeReleaseMap.makeReleaseMap), T.bind("tp", ({ a, innerReleaseMap, outerReleaseMap }) => restore( pipe( T.provideAll_( pipe( scope(layer), managedChain((_) => _(this)) ).effect, Tp.tuple(a, innerReleaseMap) ), T.result, T.chain((e) => { switch (e._tag) { case "Failure": { return pipe( promise, halt(e.cause), T.chain( () => releaseAll.releaseAll( e, sequential )(innerReleaseMap) as T.IO<E, any> ), T.chain(() => T.halt(e.cause)) ) } case "Success": { return pipe( T.do, T.tap(() => finalizerRef.set((e) => T.whenM( pipe( observers, R.modify((n) => Tp.tuple(n === 1, n - 1)) ) )( releaseAll.releaseAll( e, sequential )(innerReleaseMap) as T.UIO<any> ) ) ), T.tap(() => pipe( observers, R.update((n) => n + 1) ) ), T.bind("outerFinalizer", () => add.add((e) => T.chain_(finalizerRef.get, (f) => f(e)) )(outerReleaseMap) ), T.tap(() => pipe(promise, succeed(e.value.get(1)))), T.map(({ outerFinalizer }) => Tp.tuple(outerFinalizer, e.value.get(1)) ) ) } } }) ) ) ), T.map(({ tp }) => tp) ) ) ), T.let("memoized", ({ finalizerRef, observers, promise }) => Tp.tuple( pipe( promise, promiseAwait, T.onExit((e) => { switch (e._tag) { case "Failure": { return T.unit } case "Success": { return pipe( observers, R.update((n) => n + 1) ) } } }) ), (e: Exit<any, any>) => T.chain_(finalizerRef.get, (f) => f(e)) ) ), T.map(({ memoized, resource }) => Tp.tuple( resource as T.Effect< Tp.Tuple<[R, ReleaseMap]>, E, Tp.Tuple<[Finalizer.Finalizer, A]> >, insert(layer[hashSym].get, memoized)(m) as ReadonlyMap< symbol, Tp.Tuple<[T.IO<any, any>, Finalizer.Finalizer]> > ) ) ) } }), T.flatten ) ) } } /** * Empty layer, useful for init cases */ export const Empty: Layer<unknown, never, unknown> = new LayerSuspend(() => identity<unknown>() )