From 2c1ee3d9fa41b6e72584b990f5a374660ff449e1 Mon Sep 17 00:00:00 2001 From: Leo Conforti Date: Tue, 24 Sep 2024 18:28:24 -0500 Subject: [PATCH] `Effect.provide(managedRuntime)` and `ManagedRuntime` is subtype of `Effect, E, never>` (#3677) --- .changeset/purple-beans-jog.md | 5 ++ .changeset/smart-fishes-sit.md | 5 ++ packages/effect/dtslint/Unify.ts | 11 +++- packages/effect/src/Effect.ts | 8 +++ packages/effect/src/ManagedRuntime.ts | 47 ++++++++++++++- packages/effect/src/internal/layer.ts | 36 ++++++++--- .../effect/src/internal/managedRuntime.ts | 60 ++++++++++++------- packages/effect/test/ManagedRuntime.test.ts | 14 +++++ 8 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 .changeset/purple-beans-jog.md create mode 100644 .changeset/smart-fishes-sit.md diff --git a/.changeset/purple-beans-jog.md b/.changeset/purple-beans-jog.md new file mode 100644 index 0000000000..b04f03b001 --- /dev/null +++ b/.changeset/purple-beans-jog.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +support ManagedRuntime in Effect.provide diff --git a/.changeset/smart-fishes-sit.md b/.changeset/smart-fishes-sit.md new file mode 100644 index 0000000000..4b2f2750f3 --- /dev/null +++ b/.changeset/smart-fishes-sit.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +`ManagedRuntime` is subtype of `Effect, E, never>` diff --git a/packages/effect/dtslint/Unify.ts b/packages/effect/dtslint/Unify.ts index 734093ffe3..d1b2668264 100644 --- a/packages/effect/dtslint/Unify.ts +++ b/packages/effect/dtslint/Unify.ts @@ -4,6 +4,7 @@ import * as Either from "effect/Either" import type * as Exit from "effect/Exit" import type * as Fiber from "effect/Fiber" import type * as FiberRef from "effect/FiberRef" +import type * as ManagedRuntime from "effect/ManagedRuntime" import type * as Micro from "effect/Micro" import type * as Option from "effect/Option" import type * as Queue from "effect/Queue" @@ -100,7 +101,11 @@ export type RuntimeFiberUnify = Unify.Unify< | Fiber.RuntimeFiber<1, 2> | Fiber.RuntimeFiber<"a", "b"> > - +// $ExpectType ManagedRuntime<1, 2> | ManagedRuntime<"a", "b"> +export type ManagedRuntimeUnify = Unify.Unify< + | ManagedRuntime.ManagedRuntime<1, 2> + | ManagedRuntime.ManagedRuntime<"a", "b"> +> // $ExpectType Queue<1> | Queue<"a"> export type QueueUnify = Unify.Unify< | Queue.Queue<1> @@ -125,7 +130,7 @@ export type ResourceUnify = Unify.Unify< | Resource.Resource > -// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> +// $ExpectType 0 | Option | Ref<1> | SynchronizedRef<1> | SubscriptionRef<1> | Deferred<1, 2> | Deferred<"a", "b"> | Fiber<"a" | 1, "b" | 2> | RuntimeFiber<"a" | 1, "b" | 2> | ManagedRuntime<1, 2> | ManagedRuntime<"a", "b"> | Queue<1> | Queue<"a"> | Dequeue<"a" | 1> | ScopedRef<1> | ScopedRef<"a"> | Resource<1, 2> | Ref<"A"> | SynchronizedRef<"A"> | SubscriptionRef<"A"> | FiberRef<12> | FiberRef<"a2"> | Resource<"a", never> | Latch | Either<1 | "A", 0 | "E"> | Effect<1 | "A", 0 | "E", "R" | "R1"> | RcRef<1 | "A", 0 | "E"> export type AllUnify = Unify.Unify< | Either.Either<1, 0> | Either.Either<"A", "E"> @@ -158,5 +163,7 @@ export type AllUnify = Unify.Unify< | Resource.Resource<1, 2> | Resource.Resource<"a"> | Effect.Latch + | ManagedRuntime.ManagedRuntime<1, 2> + | ManagedRuntime.ManagedRuntime<"a", "b"> | 0 > diff --git a/packages/effect/src/Effect.ts b/packages/effect/src/Effect.ts index ca890bbb2d..2a2f95508b 100644 --- a/packages/effect/src/Effect.ts +++ b/packages/effect/src/Effect.ts @@ -38,6 +38,7 @@ import * as _runtime from "./internal/runtime.js" import * as _schedule from "./internal/schedule.js" import type * as Layer from "./Layer.js" import type { LogLevel } from "./LogLevel.js" +import type * as ManagedRuntime from "./ManagedRuntime.js" import type * as Metric from "./Metric.js" import type * as MetricLabel from "./MetricLabel.js" import type * as Option from "./Option.js" @@ -3330,12 +3331,19 @@ export const provide: { ): (self: Effect) => Effect> (context: Context.Context): (self: Effect) => Effect> (runtime: Runtime.Runtime): (self: Effect) => Effect> + ( + managedRuntime: ManagedRuntime.ManagedRuntime + ): (self: Effect) => Effect> ( self: Effect, layer: Layer.Layer ): Effect> (self: Effect, context: Context.Context): Effect> (self: Effect, runtime: Runtime.Runtime): Effect> + ( + self: Effect, + runtime: ManagedRuntime.ManagedRuntime + ): Effect> } = layer.effect_provide /** diff --git a/packages/effect/src/ManagedRuntime.ts b/packages/effect/src/ManagedRuntime.ts index d66b50dbd9..968fb4a393 100644 --- a/packages/effect/src/ManagedRuntime.ts +++ b/packages/effect/src/ManagedRuntime.ts @@ -6,8 +6,30 @@ import type * as Exit from "./Exit.js" import type * as Fiber from "./Fiber.js" import * as internal from "./internal/managedRuntime.js" import type * as Layer from "./Layer.js" -import type { Pipeable } from "./Pipeable.js" import type * as Runtime from "./Runtime.js" +import type * as Unify from "./Unify.js" + +/** + * @since 3.9.0 + * @category symbol + */ +export const TypeId: unique symbol = internal.TypeId as TypeId + +/** + * @since 3.9.0 + * @category symbol + */ +export type TypeId = typeof TypeId + +/** + * Checks if the provided argument is a `ManagedRuntime`. + * + * @param input - The value to be checked if it is a `ManagedRuntime`. + + * @since 3.9.0 + * @category guards + */ +export const isManagedRuntime: (input: unknown) => input is ManagedRuntime = internal.isManagedRuntime /** * @since 3.4.0 @@ -29,7 +51,8 @@ export declare namespace ManagedRuntime { * @since 2.0.0 * @category models */ -export interface ManagedRuntime extends Pipeable { +export interface ManagedRuntime extends Effect.Effect, ER> { + readonly [TypeId]: TypeId readonly memoMap: Layer.MemoMap readonly runtimeEffect: Effect.Effect, ER> readonly runtime: () => Promise> @@ -103,6 +126,26 @@ export interface ManagedRuntime extends Pipeable { * Dispose of the resources associated with the runtime. */ readonly disposeEffect: Effect.Effect + + readonly [Unify.typeSymbol]?: unknown + readonly [Unify.unifySymbol]?: ManagedRuntimeUnify + readonly [Unify.ignoreSymbol]?: ManagedRuntimeUnifyIgnore +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ManagedRuntimeUnify extends Effect.EffectUnify { + ManagedRuntime?: () => Extract> +} + +/** + * @category models + * @since 3.9.0 + */ +export interface ManagedRuntimeUnifyIgnore extends Effect.EffectUnifyIgnore { + Effect?: true } /** diff --git a/packages/effect/src/internal/layer.ts b/packages/effect/src/internal/layer.ts index ca60bd5414..985e10a149 100644 --- a/packages/effect/src/internal/layer.ts +++ b/packages/effect/src/internal/layer.ts @@ -10,6 +10,7 @@ import type { LazyArg } from "../Function.js" import { dual, pipe } from "../Function.js" import * as HashMap from "../HashMap.js" import type * as Layer from "../Layer.js" +import type * as ManagedRuntime from "../ManagedRuntime.js" import { pipeArguments } from "../Pipeable.js" import { hasProperty } from "../Predicate.js" import type * as Runtime from "../Runtime.js" @@ -1288,6 +1289,8 @@ const provideSomeRuntime = dual< ) }) +const ManagedRuntimeTypeId: ManagedRuntime.TypeId = Symbol.for("effect/ManagedRuntime") as ManagedRuntime.TypeId + /** @internal */ export const effect_provide = dual< { @@ -1300,6 +1303,9 @@ export const effect_provide = dual< ( runtime: Runtime.Runtime ): (self: Effect.Effect) => Effect.Effect> + ( + managedRuntime: ManagedRuntime.ManagedRuntime + ): (self: Effect.Effect) => Effect.Effect> }, { ( @@ -1314,16 +1320,32 @@ export const effect_provide = dual< self: Effect.Effect, runtime: Runtime.Runtime ): Effect.Effect> + ( + self: Effect.Effect, + managedRuntime: ManagedRuntime.ManagedRuntime + ): Effect.Effect> } >( 2, ( self: Effect.Effect, - source: Layer.Layer | Context.Context | Runtime.Runtime - ): Effect.Effect> => - isLayer(source) - ? provideSomeLayer(self, source as Layer.Layer) - : Context.isContext(source) - ? core.provideSomeContext(self, source) - : provideSomeRuntime(self, source as Runtime.Runtime) + source: + | Layer.Layer + | Context.Context + | Runtime.Runtime + | ManagedRuntime.ManagedRuntime + ): Effect.Effect> => { + if (isLayer(source)) { + return provideSomeLayer(self, source as Layer.Layer) + } else if (Context.isContext(source)) { + return core.provideSomeContext(self, source) + } else if (ManagedRuntimeTypeId in source) { + return core.flatMap( + (source as ManagedRuntime.ManagedRuntime).runtimeEffect, + (rt) => provideSomeRuntime(self, rt) + ) + } else { + return provideSomeRuntime(self, source as Runtime.Runtime) + } + } ) diff --git a/packages/effect/src/internal/managedRuntime.ts b/packages/effect/src/internal/managedRuntime.ts index a647fabc57..c9a38711d5 100644 --- a/packages/effect/src/internal/managedRuntime.ts +++ b/packages/effect/src/internal/managedRuntime.ts @@ -1,9 +1,11 @@ import type * as Effect from "../Effect.js" +import * as Effectable from "../Effectable.js" import type { Exit } from "../Exit.js" import type * as Fiber from "../Fiber.js" import type * as Layer from "../Layer.js" -import type { ManagedRuntime } from "../ManagedRuntime.js" +import type * as M from "../ManagedRuntime.js" import { pipeArguments } from "../Pipeable.js" +import { hasProperty } from "../Predicate.js" import type * as Runtime from "../Runtime.js" import * as Scope from "../Scope.js" import * as effect from "./core-effect.js" @@ -12,11 +14,17 @@ import * as fiberRuntime from "./fiberRuntime.js" import * as internalLayer from "./layer.js" import * as internalRuntime from "./runtime.js" -interface ManagedRuntimeImpl extends ManagedRuntime { +interface ManagedRuntimeImpl extends M.ManagedRuntime { readonly scope: Scope.CloseableScope cachedRuntime: Runtime.Runtime | undefined } +/** @internal */ +export const TypeId: M.TypeId = Symbol.for("effect/ManagedRuntime") as M.TypeId + +/** @internal */ +export const isManagedRuntime = (u: unknown): u is M.ManagedRuntime => hasProperty(u, TypeId) + function provide( managed: ManagedRuntimeImpl, effect: Effect.Effect @@ -32,34 +40,42 @@ function provide( ) } +const ManagedRuntimeProto = { + ...Effectable.CommitPrototype, + [TypeId]: TypeId, + pipe() { + return pipeArguments(this, arguments) + }, + commit(this: ManagedRuntimeImpl) { + return this.runtimeEffect + } +} + /** @internal */ export const make = ( layer: Layer.Layer, memoMap?: Layer.MemoMap -): ManagedRuntime => { +): M.ManagedRuntime => { memoMap = memoMap ?? internalLayer.unsafeMakeMemoMap() const scope = internalRuntime.unsafeRunSyncEffect(fiberRuntime.scopeMake()) - const self: ManagedRuntimeImpl = { + const runtimeEffect = internalRuntime.unsafeRunSyncEffect( + effect.memoize( + core.tap( + Scope.extend( + internalLayer.toRuntimeWithMemoMap(layer, memoMap), + scope + ), + (rt) => { + self.cachedRuntime = rt + } + ) + ) + ) + const self: ManagedRuntimeImpl = Object.assign(Object.create(ManagedRuntimeProto), { memoMap, scope, - runtimeEffect: internalRuntime - .unsafeRunSyncEffect( - effect.memoize( - core.tap( - Scope.extend( - internalLayer.toRuntimeWithMemoMap(layer, memoMap), - scope - ), - (rt) => { - self.cachedRuntime = rt - } - ) - ) - ), + runtimeEffect, cachedRuntime: undefined, - pipe() { - return pipeArguments(this, arguments) - }, runtime() { return self.cachedRuntime === undefined ? internalRuntime.unsafeRunPromiseEffect(self.runtimeEffect) : @@ -110,6 +126,6 @@ export const make = ( internalRuntime.unsafeRunPromiseEffect(provide(self, effect), options) : internalRuntime.unsafeRunPromise(self.cachedRuntime)(effect, options) } - } + }) return self } diff --git a/packages/effect/test/ManagedRuntime.test.ts b/packages/effect/test/ManagedRuntime.test.ts index 4877112a1b..defe8e4445 100644 --- a/packages/effect/test/ManagedRuntime.test.ts +++ b/packages/effect/test/ManagedRuntime.test.ts @@ -3,6 +3,7 @@ import * as Context from "effect/Context" import * as Effect from "effect/Effect" import * as FiberRef from "effect/FiberRef" import * as Layer from "effect/Layer" +import * as it from "effect/test/utils/extend" import { assert, describe, test } from "vitest" describe.concurrent("ManagedRuntime", () => { @@ -48,4 +49,17 @@ describe.concurrent("ManagedRuntime", () => { await runtimeB.dispose() assert.strictEqual(count, 1) }) + + it.effect( + "is subtype of effect", + () => + Effect.gen(function*() { + const tag = Context.GenericTag("string") + const layer = Layer.succeed(tag, "test") + const managedRuntime = ManagedRuntime.make(layer) + const runtime = yield* managedRuntime + const result = Context.get(runtime.context, tag) + assert.strictEqual(result, "test") + }) + ) })