import type { Erase } from "@principia/prelude/Utils"; import * as A from "../../Array"; import { pipe, tuple } from "../../Function"; import type * as H from "../../Has"; import { mergeEnvironments, tag } from "../../Has"; import { insert } from "../../Map"; import { sequential } from "../ExecutionStrategy"; import type { Cause } from "../Exit/Cause"; import type { Exit } from "../Exit/model"; import type { Managed } from "../Managed/model"; import type { Finalizer, ReleaseMap } from "../Managed/ReleaseMap"; import * as RelMap from "../Managed/ReleaseMap"; import * as XP from "../XPromise"; import * as XR from "../XRef"; import * as XRM from "../XRefM"; import * as M from "./_internal/managed"; import * as T from "./_internal/task"; import type { Layer, MergeA, MergeE, MergeR } from "./model"; import { LayerAllParInstruction, LayerAllSeqInstruction, LayerFoldInstruction, LayerInstructionTag, LayerManagedInstruction, LayerMapBothParInstruction, LayerMapBothSeqInstruction } from "./model"; export * from "./model"; export type RIO = Layer; export const _build = (layer: Layer): Managed Managed> => { const I = layer._I(); switch (I._tag) { case LayerInstructionTag.Fresh: { return M.succeed(() => build(I.layer)); } case LayerInstructionTag.Managed: { return M.succeed(() => I.managed); } case LayerInstructionTag.Suspend: { return M.succeed((memo) => memo.getOrElseMemoize(I.factory())); } case LayerInstructionTag.Map: { return M.succeed((memo) => M.map_(memo.getOrElseMemoize(I.layer), I.f)); } case LayerInstructionTag.Chain: { return M.succeed((memo) => M.chain_(memo.getOrElseMemoize(I.layer), (a) => memo.getOrElseMemoize(I.f(a)))); } case LayerInstructionTag.MapBothPar: { return M.succeed((memo) => M.mapBothPar_(memo.getOrElseMemoize(I.layer), memo.getOrElseMemoize(I.that), I.f)); } case LayerInstructionTag.MapBothSeq: { return M.succeed((memo) => M.mapBoth_(memo.getOrElseMemoize(I.layer), memo.getOrElseMemoize(I.that), I.f)); } case LayerInstructionTag.AllPar: { return M.succeed((memo) => { return pipe( M.traverseIPar_(I.layers as Layer[], memo.getOrElseMemoize), M.map(A.reduce({} as any, (b, a) => ({ ...b, ...a }))) ); }); } case LayerInstructionTag.AllSeq: { return M.succeed((memo) => { return pipe( M.traverseI_(I.layers as Layer[], memo.getOrElseMemoize), M.map(A.reduce({} as any, (b, a) => ({ ...b, ...a }))) ); }); } case LayerInstructionTag.Fold: { return M.succeed((memo) => M.foldCauseM_( memo.getOrElseMemoize(I.layer), (e) => pipe( T.toManaged()(T.ask()), M.chain((r) => M.provideSome_(memo.getOrElseMemoize(I.onFailure), () => tuple(r, e))) ), (r) => M.provideSome_(memo.getOrElseMemoize(I.onSuccess), (x) => typeof x === "object" && typeof r === "object" ? { ...x, ...r } : r ) ) ); } } }; export const build = (_: Layer): M.Managed => pipe( M.do, M.bindS("memoMap", () => M.fromTask(makeMemoMap())), M.bindS("run", () => _build(_)), M.bindS("value", ({ memoMap, run }) => run(memoMap)), M.map(({ value }) => value) ); export const pure = (has: H.Tag) => (resource: T) => new LayerManagedInstruction(M.chain_(M.fromTask(T.pure(resource)), (a) => environmentFor(has, a))); export const prepare = (has: H.Tag) => (acquire: T.Task) => ({ open: (open: (_: A) => T.Task) => ({ release: (release: (_: A) => T.Task) => fromManaged(has)( M.chain_( M.makeExit_(acquire, (a) => release(a)), (a) => M.fromTask(T.map_(open(a), () => a)) ) ) }), release: (release: (_: A) => T.Task) => fromManaged(has)(M.makeExit_(acquire, (a) => release(a))) }); export const create = (has: H.Tag) => ({ fromTask: fromTask(has), fromManaged: fromManaged(has), pure: pure(has), prepare: prepare(has) }); export const fromTask = (has: H.Tag) => (resource: T.Task) => new LayerManagedInstruction(M.chain_(M.fromTask(resource), (a) => environmentFor(has, a))); export const fromManaged = (has: H.Tag) => (resource: Managed): Layer> => new LayerManagedInstruction(M.chain_(resource, (a) => environmentFor(has, a))); export const fromRawManaged = (resource: Managed): Layer => new LayerManagedInstruction(resource); export const fromRawTask = (resource: T.Task): Layer => new LayerManagedInstruction(M.fromTask(resource)); export const fromRawFunction = (f: (a: A) => B) => fromRawTask(T.asks(f)); export const fromRawFunctionM = (f: (a: A) => T.Task) => fromRawTask(T.asksM(f)); export const both_ = ( left: Layer, right: Layer ): Layer => new LayerMapBothSeqInstruction(left, right, tuple); export const both = (right: Layer) => (left: Layer) => both_(left, right); export const and_ = ( left: Layer, right: Layer ): Layer => new LayerMapBothParInstruction(left, right, (l, r) => ({ ...l, ...r })); export const and = (right: Layer) => (left: Layer) => and_(left, right); export const fold_ = ( layer: Layer, onFailure: Layer], E1, B>, onSuccess: Layer ): Layer => new LayerFoldInstruction(layer, onFailure, onSuccess); export const andTo: { (right: Layer, noErase: "no-erase"): ( left: Layer ) => Layer; (right: Layer): (left: Layer) => Layer & R1, E | E1, A & A1>; } = (right: Layer) => (left: Layer) => andTo_(left, right); export const andTo_: { (left: Layer, right: Layer, noErase: "no-erase"): Layer< R & R1, E | E1, A & A1 >; (left: Layer, right: Layer): Layer & R1, E | E1, A & A1>; } = (left: Layer, right: Layer): Layer & R2, E | E2, A & A2> => fold_ & R2, E2, A2, E2, never, Erase & R2, E | E2, A2 & A>( right, fromRawFunctionM((_: readonly [R & R2, Cause]) => T.halt(_[1])), and_(right, left) ); export const to = (to: Layer) => (layer: Layer) => to_(layer, to); export const to_ = ( layer: Layer, to: Layer ): Layer & R2, E | E2, A> => fold_ & R2, E2, A2, E2, never, Erase & R2, E | E2, A>( layer, fromRawFunctionM((_: readonly [R & R2, Cause]) => T.halt(_[1])), to ); export const andSeq_ = ( layer: Layer, that: Layer ): Layer => new LayerMapBothSeqInstruction(layer, that, (l, r) => ({ ...l, ...r })); export const andSeq = (that: Layer) => (layer: Layer) => andSeq_(layer, that); export const all = []>( ...ls: Ls & { 0: Layer } ): Layer, MergeE, MergeA> => new LayerAllParInstruction(ls); export const allPar = []>( ...ls: Ls & { 0: Layer } ): Layer, MergeE, MergeA> => new LayerAllSeqInstruction(ls); function environmentFor(has: H.Tag, a: T): Managed>; function environmentFor(has: H.Tag, a: T): Managed { return M.fromTask( T.asks((r) => ({ [has.key]: mergeEnvironments(has, r, a as any)[has.key] })) ); } /** * A `MemoMap` memoizes dependencies. */ export class MemoMap { constructor(readonly ref: XRM.RefM, 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) => new M.Managed( pipe( this.ref, XRM.modify((m) => { const inMap = m.get(layer.hash.get); if (inMap) { const [acquire, release] = inMap; const cached = T.asksM(([_, rm]: readonly [R, ReleaseMap]) => pipe( acquire as T.EIO, T.onExit((ex) => { switch (ex._tag) { case "Success": { return RelMap.add(release)(rm); } case "Failure": { return T.unit(); } } }), T.map((x) => [release, x] as readonly [Finalizer, A]) ) ); return T.pure(tuple(cached, m)); } else { return pipe( T.do, T.bindS("observers", () => XR.makeRef(0)), T.bindS("promise", () => XP.make()), T.bindS("finalizerRef", () => XR.makeRef(RelMap.noopFinalizer)), T.letS("resource", ({ finalizerRef, observers, promise }) => T.uninterruptibleMask(({ restore }) => pipe( T.do, T.bindS("env", () => T.ask()), T.letS("a", ({ env: [a] }) => a), T.letS("outerReleaseMap", ({ env: [_, outerReleaseMap] }) => outerReleaseMap), T.bindS("innerReleaseMap", () => RelMap.makeReleaseMap), T.bindS("tp", ({ a, innerReleaseMap, outerReleaseMap }) => restore( pipe( T.giveAll_( pipe( _build(layer), M.chain((_) => _(this)) ).task, [a, innerReleaseMap] ), T.result, T.chain((e) => { switch (e._tag) { case "Failure": { return pipe( promise, XP.halt(e.cause), T.chain( () => M.releaseAll(e, sequential())(innerReleaseMap) as T.EIO ), T.chain(() => T.halt(e.cause)) ); } case "Success": { return pipe( T.do, T.tap(() => finalizerRef.set((e) => T.whenM( pipe( observers, XR.modify((n) => [n === 1, n - 1]) ) )(M.releaseAll(e, sequential())(innerReleaseMap) as T.IO) ) ), T.tap(() => pipe( observers, XR.update((n) => n + 1) ) ), T.bindS("outerFinalizer", () => RelMap.add((e) => T.chain_(finalizerRef.get, (f) => f(e)))( outerReleaseMap ) ), T.tap(() => pipe(promise, XP.succeed(e.value[1]))), T.map(({ outerFinalizer }) => tuple(outerFinalizer, e.value[1])) ); } } }) ) ) ), T.map(({ tp }) => tp) ) ) ), T.letS( "memoized", ({ finalizerRef, observers, promise }) => [ pipe( promise, XP.await, T.onExit((e) => { switch (e._tag) { case "Failure": { return T.unit(); } case "Success": { return pipe( observers, XR.update((n) => n + 1) ); } } }) ), (e: Exit) => T.chain_(finalizerRef.get, (f) => f(e)) ] as readonly [T.EIO, Finalizer] ), T.map(({ memoized, resource }) => tuple( resource as T.Task, insert(layer.hash.get, memoized)(m) as ReadonlyMap< PropertyKey, readonly [T.EIO, Finalizer] > ) ) ); } }), T.flatten ) ); } export const HasMemoMap = tag(MemoMap); export type HasMemoMap = H.HasTag; export function makeMemoMap() { return pipe( XRM.makeRefM, Finalizer]>>(new Map()), T.chain((r) => T.total(() => new MemoMap(r))) ); }