// 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(resource: T.Effect): Layer { return new LayerManaged(managedFromEffect(resource)) } /** * Creates a layer from a function */ export function fromRawFunction(f: (a: A) => B) { return fromRawEffect(T.access(f)) } /** * Creates a layer from an effectful function */ export function fromRawFunctionM(f: (a: A) => T.Effect) { return fromRawEffect(T.accessM(f)) } /** * Creates a layer from a managed environment */ export function fromRawManaged(resource: Managed): Layer { return new LayerManaged(resource) } /** * Constructs a layer that passes along the specified environment as an * output. */ export function identity() { return fromRawManaged(managedEnvironment()) } /** * 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_( self: Layer, that: Layer ): Layer { 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( that: Layer ): (self: Layer) => Layer { 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(self: Layer) { return (failure: Layer]>, E2, A2>) => (success: Layer): Layer => new LayerFold(self, failure, success) } /** * Use `from` to partially provide environment into `to` */ export function using(from: Layer) { return (to: Layer): Layer => compose_(from["+++"](identity()), to) } /** * Use `from` to partially provide environment into `to` and merge both */ export function usingAnd(from: Layer) { return (to: Layer): Layer => compose_(from["+++"](identity()), to["+++"](identity())) } /** * Compose layers */ export function compose_( from: Layer, to: Layer ): Layer { return fold(from)( fromRawFunctionM((_: Tp.Tuple<[unknown, C.Cause]>) => T.halt(_.get(1))) )(to) } /** * Compose layers */ export function compose( to: Layer ): (from: Layer) => Layer { return (from) => compose_(from, to) } export const hashSym: unique symbol = Symbol() export abstract class Layer { readonly [hashSym] = new AtomicReference(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 */ ["<=<"](that: Layer): Layer { return that[">=>"](this) } /** * Use this Layer to provide data to that */ [">=>"](that: Layer): Layer { return compose_(this, that) } /** * Use that Layer to partially provide data to this */ ["<<<"]( that: Layer ): Layer & R2, E2 | E, ROut> { return that[">>>"](this) } /** * Use this Layer to partially provide data to that */ [">>>"](that: Layer): Layer & RIn, E2 | E, A2> [">>>"](that: Layer): Layer { return this["+++"](identity())[">=>"](that) } /** * Create a Layer with the data from both Layers, while providing the data from this to that */ [">+>"]( that: Layer ): Layer, E2 | E, ROut & A2> { return this[">>>"](that["+++"](identity())) } /** * Create a Layer with the data from both Layers, while providing the data from that to this */ ["<+<"]( that: Layer ): Layer & R2, E | E2, ROut & A2> { return that[">+>"](this) } /** * Combine both layers in parallel */ ["+++"](from: Layer): Layer { return and_(from, this) } /** * Use the layer to provide partial environment to an effect */ use(effect: T.Effect): T.Effect { return provideSomeLayer(this)(effect) } /** * Use the layer to provide the full environment to an effect */ useAll(effect: T.Effect): T.Effect { return provideLayer(this)(effect) } /** * Use the layer to provide the full environment to an effect */ get useForever(): T.Effect { return provideLayer(this)(T.never) } } /** * Provides a layer to the given effect */ export function provideSomeLayer(layer: Layer) { return (self: T.Effect): T.Effect => provideLayer_(self, layer["+++"](identity())) } /** * Provides a layer to the given effect */ export function provideSomeLayer_( self: T.Effect, layer: Layer ): T.Effect { return provideLayer_(self, layer["+++"](identity())) } /** * Provides a layer to the given effect */ export function provideLayer_( self: T.Effect, layer: Layer ): T.Effect { return use_(build(layer), (p) => T.provideAll_(self, p)) } /** * Provides a layer to the given effect */ export function provideLayer(layer: Layer) { return (self: T.Effect) => provideLayer_(self, layer) } export type LayerInstruction = | LayerFold | LayerFresh | LayerManaged | LayerSuspend | LayerZipWithPar | LayerZipWithSeq | LayerAllPar | LayerAllSeq | LayerMap | LayerChain export class LayerFold extends Layer< R & R2, E2 | E3, A2 | A3 > { readonly _tag = "LayerFold" constructor( readonly self: Layer, readonly failure: Layer]>, E2, A2>, readonly success: Layer ) { super() } } export class LayerMap extends Layer { readonly _tag = "LayerMap" constructor(readonly self: Layer, readonly f: (a: ROut) => ROut1) { super() } } export class LayerChain extends Layer< RIn & RIn2, E | E2, ROut1 > { readonly _tag = "LayerChain" constructor( readonly self: Layer, readonly f: (a: ROut) => Layer ) { super() } } export class LayerFresh extends Layer { readonly _tag = "LayerFresh" constructor(readonly self: Layer) { super() } } export class LayerManaged extends Layer { readonly _tag = "LayerManaged" constructor(readonly self: Managed) { super() } } export class LayerSuspend extends Layer { readonly _tag = "LayerSuspend" constructor(readonly self: () => Layer) { super() } } export class LayerZipWithPar extends Layer< RIn & RIn1, E | E1, ROut3 > { readonly _tag = "LayerZipWithPar" constructor( readonly self: Layer, readonly that: Layer, readonly f: (s: ROut, t: ROut2) => ROut3 ) { super() } } export type MergeR[]> = UnionToIntersection< { [k in keyof Ls]: [Ls[k]] extends [Layer] ? unknown extends X ? never : X : never }[number] > export type MergeE[]> = { [k in keyof Ls]: [Ls[k]] extends [Layer] ? X : never }[number] export type MergeA[]> = UnionToIntersection< { [k in keyof Ls]: [Ls[k]] extends [Layer] ? unknown extends X ? never : X : never }[number] > export class LayerAllPar[]> extends Layer< MergeR, MergeE, MergeA > { readonly _tag = "LayerAllPar" constructor(readonly layers: Layers & { 0: Layer }) { super() } } export class LayerAllSeq[]> extends Layer< MergeR, MergeE, MergeA > { readonly _tag = "LayerAllSeq" constructor(readonly layers: Layers & { 0: Layer }) { super() } } export class LayerZipWithSeq extends Layer< RIn & RIn1, E | E1, ROut3 > { readonly _tag = "LayerZipWithSeq" constructor( readonly self: Layer, readonly that: Layer, readonly f: (s: ROut, t: ROut2) => ROut3 ) { super() } } export function scope( _: Layer ): Managed Managed> { 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[], memo.getOrElseMemoize), managedMap(chunkReduce({} as any, (b, a) => ({ ...b, ...a }))) ) }) } case "LayerAllSeq": { return succeed_1((memo) => { return pipe( managedForEach_(I.layers as Layer[], 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()), 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(_: Layer): Managed { 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, 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, 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 = (layer: Layer) => { return managedApply( 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, 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()), T.bind("finalizerRef", () => R.makeRef(Finalizer.noopFinalizer) ), T.let("resource", ({ finalizerRef, observers, promise }) => T.uninterruptibleMask(({ restore }) => pipe( T.do, T.bind("env", () => T.environment>()), 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 ), 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 ) ) ), 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) => 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, Finalizer.Finalizer]> > ) ) ) } }), T.flatten ) ) } } /** * Empty layer, useful for init cases */ export const Empty: Layer = new LayerSuspend(() => identity() )