/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable prefer-destructuring */ // eslint-disable-next-line @typescript-eslint/no-unused-vars import * as Def from "effect/Deferred" import * as Effect from "effect/Effect" import * as Option from "effect/Option" import * as Ref from "effect/Ref" import type { Scope } from "effect/Scope" import type { Semaphore } from "effect/Semaphore" import type * as Context from "./Context.js" import { curry } from "./Function.js" import { typedKeysOf } from "./utils.js" export * from "effect/Effect" // v4: Effect interface not re-exported by `export *` due to local binding collision export type { Effect } from "effect/Effect" export function flatMapOption( self: Effect.Effect, E, R>, fm: (a: A) => Effect.Effect ): Effect.Effect, E | E2, R | R2> { return Effect.flatMap(self, (d) => Option.match(d, { onNone: () => Effect.sync(() => Option.none()), onSome: (_) => Effect.map(fm(_), Option.some) })) } export function tapOption( self: Effect.Effect, E, R>, fm: (a: A) => Effect.Effect ): Effect.Effect, E | E2, R | R2> { return Effect.flatMap(self, (d) => Option.match(d, { onNone: () => Effect.sync(() => Option.none()), onSome: (_) => Effect.map(fm(_), () => Option.some(_)) })) } export function zipRightOption( self: Effect.Effect, E, R>, fm: Effect.Effect ) { return Effect.flatMap(self, (d) => Option.match(d, { onNone: () => Effect.sync(() => Option.none()), onSome: (_) => Effect.map(fm, () => Option.some(_)) })) } export function mapOption( self: Effect.Effect, E, R>, fm: (a: A) => A2 ): Effect.Effect, E, R> { return Effect.map(self, (d) => Option.match(d, { onNone: () => Option.none(), onSome: (_) => Option.some(fm(_)) })) } /** * Takes [A, B], applies it to a curried Effect function, * taps the Effect, returning A. */ export function tupleTap( f: (b: B) => (a: A) => Effect.Effect ) { return (t: readonly [A, B]) => Effect.sync(() => t[0]).pipe(Effect.tap(f(t[1]))) } /** * Takes [A, B], applies it to an Effect function, * taps the Effect, returning A. */ export function tupleTap_(f: (a: A, b: B) => Effect.Effect) { return tupleTap(curry(f)) } export function ifDiffR(f: (i: I) => Effect.Effect) { return (n: I, orig: I) => ifDiff_(n, orig, f) } export function ifDiff_( n: I, orig: I, f: (i: I) => Effect.Effect ) { return n !== orig ? f(n) : Effect.void } export function ifDiff(n: I, orig: I) { return (f: (i: I) => Effect.Effect) => ifDiff_(n, orig, f) } // NOTE: await extension doesnt work via tsplus somehow export const await_ = Def.await /** * Ref has atomic modify support if synchronous, for Effect we need a Semaphore. */ export function modifyWithPermitWithEffect(ref: Ref.Ref, semaphore: Semaphore) { const withPermit = semaphore.withPermits(1) return (mod: (a: A) => Effect.Effect) => withPermit( Effect .flatMap(Ref.get(ref), mod) .pipe( Effect.tap(([, _]) => Ref.set(ref, _)), Effect.map(([_]) => _) ) ) } type ServiceA = T extends Effect.Effect ? S : T extends Context.Service ? S : never type ServiceR = T extends Effect.Effect ? R : T extends Context.Service ? I : never type ServiceE = T extends Effect.Effect ? E : never // type Values = T extends { [s: string]: infer S } ? ServiceA : never type ValuesR = T extends { [s: string]: infer S } ? ServiceR : never type ValuesE = T extends { [s: string]: infer S } ? ServiceE : never /** * Due to tsplus unification (tsplus unify tag), when trying to use the Effect type in a type constraint * the compiler will cause basically anything to match. as such, use this type instead. * ```ts * const a = < * SVC extends Record< * string, * ((req: number) => Effect.Effect) | Effect.Effect * > * >(svc: SVC) => svc * * const b = a({ str: "" }) // valid, but shouldn't be! * ``` */ // eslint-disable-next-line import/namespace export interface EffectUnunified extends Effect.Effect {} export type LowerFirst = S extends `${infer First}${infer Rest}` ? `${Lowercase}${Rest}` : S export type LowerServices | Effect.Effect>> = { [key in keyof T as LowerFirst]: ServiceA } export function allLower | Effect.Effect>>( services: T ) { return Effect.all( typedKeysOf(services).reduce((prev, cur) => { const svc = services[cur]! prev[((cur as string)[0]!.toLowerCase() + (cur as string).slice(1)) as unknown as LowerFirst] = svc return prev }, {} as any), { concurrency: "inherit" } ) as any as Effect.Effect, ValuesE, ValuesR> } export function allLowerWith | Effect.Effect>, A>( services: T, fn: (services: LowerServices) => A ) { return Effect.map(allLower(services), fn) } export function allLowerWithEffect< T extends Record | Effect.Effect>, R, E, A >( services: T, fn: (services: LowerServices) => Effect.Effect ) { return Effect.flatMap(allLower(services), fn) } /** * Recovers from all errors. */ export function catchAllMap(f: (e: E) => A2) { return (self: Effect.Effect): Effect.Effect => Effect.catch(self, (err: E) => Effect.sync(() => f(err))) } /** * Annotates each log in this scope with the specified log annotation. */ export function annotateLogscoped(key: string, value: string): Effect.Effect { return Effect.annotateLogsScoped(key, value) } /** * Annotates each log in this scope with the specified log annotations. */ export function annotateLogsScoped(kvps: Record): Effect.Effect { return Effect.annotateLogsScoped(kvps) }