/** * @since 2.0.0 */ import type * as Context from "./Context.ts" import * as Effect from "./Effect.ts" import * as Exit from "./Exit.ts" import * as Fiber from "./Fiber.ts" import * as Layer from "./Layer.ts" import { hasProperty } from "./Predicate.ts" import * as Scope from "./Scope.ts" import type { Mutable } from "./Types.ts" const TypeId = "~effect/ManagedRuntime" /** * Checks if the provided argument is a `ManagedRuntime`. * * @since 3.9.0 * @category guards */ export const isManagedRuntime = (input: unknown): input is ManagedRuntime => hasProperty(input, TypeId) /** * @since 3.4.0 */ export declare namespace ManagedRuntime { /** * @category type-level * @since 4.0.0 */ export type Services> = [T] extends [ManagedRuntime] ? R : never /** * @category type-level * @since 3.4.0 */ export type Error> = [T] extends [ManagedRuntime] ? E : never } /** * @since 2.0.0 * @category models */ export interface ManagedRuntime { readonly [TypeId]: typeof TypeId readonly memoMap: Layer.MemoMap readonly contextEffect: Effect.Effect, ER> readonly context: () => Promise> // internal readonly scope: Scope.Closeable // internal cachedContext: Context.Context | undefined /** * Executes the effect using the provided Scheduler or using the global * Scheduler if not provided */ readonly runFork: ( self: Effect.Effect, options?: Effect.RunOptions ) => Fiber.Fiber /** * Executes the effect synchronously returning the exit. * * This method is effectful and should only be invoked at the edges of your * program. */ readonly runSyncExit: (effect: Effect.Effect) => Exit.Exit /** * Executes the effect synchronously throwing in case of errors or async boundaries. * * This method is effectful and should only be invoked at the edges of your * program. */ readonly runSync: (effect: Effect.Effect) => A /** * Executes the effect asynchronously, eventually passing the exit value to * the specified callback. * * This method is effectful and should only be invoked at the edges of your * program. */ readonly runCallback: ( effect: Effect.Effect, options?: | Effect.RunOptions & { readonly onExit: (exit: Exit.Exit) => void } | undefined ) => (interruptor?: number | undefined) => void /** * Runs the `Effect`, returning a JavaScript `Promise` that will be resolved * with the value of the effect once the effect has been executed, or will be * rejected with the first error or exception throw by the effect. * * This method is effectful and should only be used at the edges of your * program. */ readonly runPromise: (effect: Effect.Effect, options?: Effect.RunOptions) => Promise /** * Runs the `Effect`, returning a JavaScript `Promise` that will be resolved * with the `Exit` state of the effect once the effect has been executed. * * This method is effectful and should only be used at the edges of your * program. */ readonly runPromiseExit: ( effect: Effect.Effect, options?: Effect.RunOptions ) => Promise> /** * Dispose of the resources associated with the runtime. */ readonly dispose: () => Promise /** * Dispose of the resources associated with the runtime. */ readonly disposeEffect: Effect.Effect } /** * Convert a Layer into an ManagedRuntime, that can be used to run Effect's using * your services. * * @since 2.0.0 * @category runtime class * @example * ```ts * import { Console, Effect, Layer, ManagedRuntime, Context } from "effect" * * class Notifications extends Context.Service Effect.Effect * }>()("Notifications") { * static readonly layer = Layer.succeed(this)({ * notify: Effect.fn("Notifications.notify")((message) => Console.log(message)) * }) * } * * async function main() { * const runtime = ManagedRuntime.make(Notifications.layer) * await runtime.runPromise(Effect.flatMap( * Notifications, * (_) => _.notify("Hello, world!") * )) * await runtime.dispose() * } * * main() * ``` */ export const make = ( layer: Layer.Layer, options?: { readonly memoMap?: Layer.MemoMap | undefined } | undefined ): ManagedRuntime => { const memoMap = options?.memoMap ?? Layer.makeMemoMapUnsafe() const scope = Scope.makeUnsafe("parallel") const layerScope = Scope.forkUnsafe(scope, "sequential") const defaultRunOptions: Effect.RunOptions = { onFiberStart: Fiber.runIn(scope) } const mergeRunOptions = (options?: O): O => options ? { ...options, onFiberStart: options.onFiberStart ? (fiber) => { defaultRunOptions.onFiberStart!(fiber) options.onFiberStart!(fiber) } : defaultRunOptions.onFiberStart } : defaultRunOptions as O let buildFiber: Fiber.Fiber, ER> | undefined const contextEffect = Effect.withFiber, ER>((fiber) => { if (!buildFiber) { buildFiber = Effect.runFork( Effect.tap( Layer.buildWithMemoMap(layer, memoMap, layerScope), (context) => Effect.sync(() => { self.cachedContext = context }) ), { ...defaultRunOptions, scheduler: fiber.currentScheduler } ) } return Effect.flatten(Fiber.await(buildFiber)) }) const self: ManagedRuntime = { [TypeId]: TypeId, memoMap, scope, contextEffect: contextEffect, cachedContext: undefined, context() { return self.cachedContext === undefined ? Effect.runPromise(self.contextEffect) : Promise.resolve(self.cachedContext) }, dispose(): Promise { return Effect.runPromise(self.disposeEffect) }, disposeEffect: Effect.suspend(() => { ;(self as Mutable>).contextEffect = Effect.die("ManagedRuntime disposed") self.cachedContext = undefined return Scope.close(self.scope, Exit.void) }), runFork(effect: Effect.Effect, options?: Effect.RunOptions): Fiber.Fiber { return self.cachedContext === undefined ? Effect.runFork(provide(self, effect), mergeRunOptions(options)) : Effect.runForkWith(self.cachedContext)(effect, mergeRunOptions(options)) }, runCallback( effect: Effect.Effect, options?: Effect.RunOptions & { readonly onExit: (exit: Exit.Exit) => void } ): (interruptor?: number | undefined) => void { return self.cachedContext === undefined ? Effect.runCallback(provide(self, effect), mergeRunOptions(options)) : Effect.runCallbackWith(self.cachedContext)(effect, mergeRunOptions(options)) }, runSyncExit(effect: Effect.Effect): Exit.Exit { return self.cachedContext === undefined ? Effect.runSyncExit(provide(self, effect)) : Effect.runSyncExitWith(self.cachedContext)(effect) }, runSync(effect: Effect.Effect): A { return self.cachedContext === undefined ? Effect.runSync(provide(self, effect)) : Effect.runSyncWith(self.cachedContext)(effect) }, runPromiseExit(effect: Effect.Effect, options?: Effect.RunOptions): Promise> { return self.cachedContext === undefined ? Effect.runPromiseExit(provide(self, effect), mergeRunOptions(options)) : Effect.runPromiseExitWith(self.cachedContext)(effect, mergeRunOptions(options)) }, runPromise(effect: Effect.Effect, options?: { readonly signal?: AbortSignal | undefined }): Promise { return self.cachedContext === undefined ? Effect.runPromise(provide(self, effect), mergeRunOptions(options)) : Effect.runPromiseWith(self.cachedContext)(effect, mergeRunOptions(options)) } } return self } function provide( managed: ManagedRuntime, effect: Effect.Effect ): Effect.Effect { return Effect.flatMap( managed.contextEffect, (context) => Effect.provideContext(effect, context) ) }