diff --git a/.changeset/famous-mugs-attack.md b/.changeset/famous-mugs-attack.md index c660098388..8040e12fad 100644 --- a/.changeset/famous-mugs-attack.md +++ b/.changeset/famous-mugs-attack.md @@ -21,9 +21,8 @@ class Notifications extends Effect.Tag("Notifications")< async function main() { const runtime = ManagedRuntime.make(Notifications.Live); - const runPromise = ManagedRuntime.runPromise(runtime); - await runPromise(Notifications.notify("Hello, world!")); - await ManagedRuntime.dispose(runtime); + await runtime.runPromise(Notifications.notify("Hello, world!")); + await runtime.dispose(); } main(); diff --git a/packages/effect/src/ManagedRuntime.ts b/packages/effect/src/ManagedRuntime.ts index a3338d2f31..b6360bc0a9 100644 --- a/packages/effect/src/ManagedRuntime.ts +++ b/packages/effect/src/ManagedRuntime.ts @@ -13,9 +13,76 @@ import type * as Runtime from "./Runtime.js" * @since 2.0.0 * @category models */ -export interface ManagedRuntime extends Pipeable { +export interface ManagedRuntime extends Pipeable { readonly memoMap: Layer.MemoMap - readonly runtime: Effect.Effect, E> + readonly runtimeEffect: Effect.Effect, ER> + readonly runtime: () => Promise> + + /** + * Executes the effect using the provided Scheduler or using the global + * Scheduler if not provided + */ + readonly runFork: ( + self: Effect.Effect, + options?: Runtime.RunForkOptions + ) => Fiber.RuntimeFiber + + /** + * 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?: Runtime.RunCallbackOptions | undefined + ) => Runtime.Cancel + + /** + * 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) => 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) => Promise> + + /** + * Dispose of the resources associated with the runtime. + */ + readonly dispose: () => Promise + + /** + * Dispose of the resources associated with the runtime. + */ + readonly disposeEffect: Effect.Effect } /** @@ -36,9 +103,8 @@ export interface ManagedRuntime extends Pipeable { * * async function main() { * const runtime = ManagedRuntime.make(Notifications.Live) - * const runPromise = ManagedRuntime.runPromise(runtime) - * await runPromise(Notifications.notify("Hello, world!")) - * await ManagedRuntime.dispose(runtime) + * await runtime.runPromise(Notifications.notify("Hello, world!")) + * await runtime.dispose() * } * * main() @@ -47,102 +113,3 @@ export const make: ( layer: Layer.Layer, memoMap?: Layer.MemoMap | undefined ) => ManagedRuntime = internal.make - -/** - * Executes the effect using the provided Scheduler or using the global - * Scheduler if not provided - * - * @since 2.0.0 - * @category execution - */ -export const runFork: ( - runtime: ManagedRuntime -) => (self: Effect.Effect, options?: Runtime.RunForkOptions) => Fiber.RuntimeFiber = - internal.runFork - -/** - * Executes the effect synchronously returning the exit. - * - * This method is effectful and should only be invoked at the edges of your - * program. - * - * @since 2.0.0 - * @category execution - */ -export const runSyncExit: ( - self: ManagedRuntime -) => (effect: Effect.Effect) => Exit.Exit = internal.runSyncExit - -/** - * 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. - * - * @since 2.0.0 - * @category execution - */ -export const runSync: (self: ManagedRuntime) => (effect: Effect.Effect) => A = - internal.runSync - -/** - * 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. - * - * @since 2.0.0 - * @category execution - */ -export const runCallback: ( - runtime: ManagedRuntime -) => ( - effect: Effect.Effect, - options?: Runtime.RunCallbackOptions | undefined -) => Runtime.Cancel = internal.runCallback - -/** - * 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. - * - * @since 2.0.0 - * @category execution - */ -export const runPromise: (self: ManagedRuntime) => (effect: Effect.Effect) => Promise = - internal.runPromise - -/** - * 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. - * - * @since 2.0.0 - * @category execution - */ -export const runPromiseExit: ( - self: ManagedRuntime -) => (effect: Effect.Effect) => Promise> = internal.runPromiseExit - -/** - * Dispose of the resources associated with the runtime. - * - * @since 2.0.0 - * @category finalization - */ -export const dispose: (self: ManagedRuntime) => Promise = internal.dispose - -/** - * Dispose of the resources associated with the runtime. - * - * @since 2.0.0 - * @category finalization - */ -export const disposeEffect: (self: ManagedRuntime) => Effect.Effect = - internal.disposeEffect diff --git a/packages/effect/src/internal/managedRuntime.ts b/packages/effect/src/internal/managedRuntime.ts index e028ad36aa..068deadefb 100644 --- a/packages/effect/src/internal/managedRuntime.ts +++ b/packages/effect/src/internal/managedRuntime.ts @@ -22,7 +22,7 @@ function provide( effect: Effect.Effect ): Effect.Effect { return core.flatMap( - managed.runtime, + managed.runtimeEffect, (rt) => core.withFiberRuntime((fiber) => { fiber.setFiberRefs(rt.fiberRefs) @@ -33,16 +33,16 @@ function provide( } /** @internal */ -export const make = ( - layer: Layer.Layer, +export const make = ( + layer: Layer.Layer, memoMap?: Layer.MemoMap -): ManagedRuntime => { +): ManagedRuntime => { memoMap = memoMap ?? internalLayer.unsafeMakeMemoMap() const scope = internalRuntime.unsafeRunSyncEffect(fiberRuntime.scopeMake()) - const self: ManagedRuntimeImpl = { + const self: ManagedRuntimeImpl = { memoMap, scope, - runtime: internalRuntime + runtimeEffect: internalRuntime .unsafeRunSyncEffect( effect.memoize( core.tap( @@ -59,79 +59,53 @@ export const make = ( cachedRuntime: undefined, pipe() { return pipeArguments(this, arguments) + }, + runtime() { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunPromiseEffect(self.runtimeEffect) : + Promise.resolve(self.cachedRuntime) + }, + dispose(): Promise { + return internalRuntime.unsafeRunPromiseEffect(self.disposeEffect) + }, + disposeEffect: core.suspend(() => { + ;(self as any).runtime = core.die("ManagedRuntime disposed") + self.cachedRuntime = undefined + return Scope.close(self.scope, core.exitUnit) + }), + runFork(effect: Effect.Effect, options?: Runtime.RunForkOptions): Fiber.RuntimeFiber { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeForkEffect(provide(self, effect), options) : + internalRuntime.unsafeFork(self.cachedRuntime)(effect, options) + }, + runSyncExit(effect: Effect.Effect): Exit { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunSyncExitEffect(provide(self, effect)) : + internalRuntime.unsafeRunSyncExit(self.cachedRuntime)(effect) + }, + runSync(effect: Effect.Effect): A { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunSyncEffect(provide(self, effect)) : + internalRuntime.unsafeRunSync(self.cachedRuntime)(effect) + }, + runPromiseExit(effect: Effect.Effect): Promise> { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunPromiseExitEffect(provide(self, effect)) : + internalRuntime.unsafeRunPromiseExit(self.cachedRuntime)(effect) + }, + runCallback( + effect: Effect.Effect, + options?: Runtime.RunCallbackOptions | undefined + ): Runtime.Cancel { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunCallback(internalRuntime.defaultRuntime)(provide(self, effect), options) : + internalRuntime.unsafeRunCallback(self.cachedRuntime)(effect, options) + }, + runPromise(effect: Effect.Effect): Promise { + return self.cachedRuntime === undefined ? + internalRuntime.unsafeRunPromiseEffect(provide(self, effect)) : + internalRuntime.unsafeRunPromise(self.cachedRuntime)(effect) } } return self } - -/** @internal */ -export const dispose = (self: ManagedRuntime): Promise => - internalRuntime.unsafeRunPromiseEffect(disposeEffect(self)) - -/** @internal */ -export const disposeEffect = (self: ManagedRuntime): Effect.Effect => - core.suspend(() => { - const impl = self as ManagedRuntimeImpl - ;(self as any).runtime = core.die("ManagedRuntime disposed") - impl.cachedRuntime = undefined - return Scope.close(impl.scope, core.exitUnit) - }) - -/** @internal */ -export const runFork = - (self: ManagedRuntime) => - (effect: Effect.Effect, options?: Runtime.RunForkOptions): Fiber.RuntimeFiber => { - const impl = self as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeForkEffect(provide(impl, effect), options) : - internalRuntime.unsafeFork(impl.cachedRuntime)(effect, options) - } - -/** @internal */ -export const runSyncExit = - (self: ManagedRuntime) => (effect: Effect.Effect): Exit => { - const impl = self as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeRunSyncExitEffect(provide(impl, effect)) : - internalRuntime.unsafeRunSyncExit(impl.cachedRuntime)(effect) - } - -/** @internal */ -export const runSync = (self: ManagedRuntime) => (effect: Effect.Effect): A => { - const impl = self as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeRunSyncEffect(provide(impl, effect)) : - internalRuntime.unsafeRunSync(impl.cachedRuntime)(effect) -} - -/** @internal */ -export const runPromiseExit = - (self: ManagedRuntime) => (effect: Effect.Effect): Promise> => { - const impl = self as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeRunPromiseExitEffect(provide(impl, effect)) : - internalRuntime.unsafeRunPromiseExit(impl.cachedRuntime)(effect) - } - -/** @internal */ -export const runCallback = ( - runtime: ManagedRuntime -) => -( - effect: Effect.Effect, - options?: Runtime.RunCallbackOptions | undefined -): Runtime.Cancel => { - const impl = runtime as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeRunCallback(internalRuntime.defaultRuntime)(provide(impl, effect), options) : - internalRuntime.unsafeRunCallback(impl.cachedRuntime)(effect, options) -} - -/** @internal */ -export const runPromise = - (self: ManagedRuntime) => (effect: Effect.Effect): Promise => { - const impl = self as ManagedRuntimeImpl - return impl.cachedRuntime === undefined ? - internalRuntime.unsafeRunPromiseEffect(provide(impl, effect)) : - internalRuntime.unsafeRunPromise(impl.cachedRuntime)(effect) - } diff --git a/packages/effect/test/ManagedRuntime.test.ts b/packages/effect/test/ManagedRuntime.test.ts index 3436b473bd..5c58ea3c5f 100644 --- a/packages/effect/test/ManagedRuntime.test.ts +++ b/packages/effect/test/ManagedRuntime.test.ts @@ -12,10 +12,9 @@ describe.concurrent("ManagedRuntime", () => { count++ })) const runtime = ManagedRuntime.make(layer) - const run = ManagedRuntime.runPromise(runtime) - await run(Effect.unit) - await run(Effect.unit) - await ManagedRuntime.dispose(runtime) + await runtime.runPromise(Effect.unit) + await runtime.runPromise(Effect.unit) + await runtime.dispose() assert.strictEqual(count, 1) }) @@ -23,18 +22,16 @@ describe.concurrent("ManagedRuntime", () => { const tag = Context.GenericTag("string") const layer = Layer.succeed(tag, "test") const runtime = ManagedRuntime.make(layer) - const run = ManagedRuntime.runPromise(runtime) - const result = await run(tag) - await ManagedRuntime.dispose(runtime) + const result = await runtime.runPromise(tag) + await runtime.dispose() assert.strictEqual(result, "test") }) test("provides fiberRefs", async () => { const layer = Layer.setRequestCaching(true) const runtime = ManagedRuntime.make(layer) - const run = ManagedRuntime.runPromise(runtime) - const result = await run(FiberRef.get(FiberRef.currentRequestCacheEnabled)) - await ManagedRuntime.dispose(runtime) + const result = await runtime.runPromise(FiberRef.get(FiberRef.currentRequestCacheEnabled)) + await runtime.dispose() assert.strictEqual(result, true) }) @@ -45,10 +42,10 @@ describe.concurrent("ManagedRuntime", () => { })) const runtimeA = ManagedRuntime.make(layer) const runtimeB = ManagedRuntime.make(layer, runtimeA.memoMap) - await ManagedRuntime.runPromise(runtimeA)(Effect.unit) - await ManagedRuntime.runPromise(runtimeB)(Effect.unit) - await ManagedRuntime.dispose(runtimeA) - await ManagedRuntime.dispose(runtimeB) + await runtimeA.runPromise(Effect.unit) + await runtimeB.runPromise(Effect.unit) + await runtimeA.dispose() + await runtimeB.dispose() assert.strictEqual(count, 1) }) })