Skip to content

Commit

Permalink
Effect.provide(managedRuntime) and ManagedRuntime<R, E> is subtyp…
Browse files Browse the repository at this point in the history
…e of `Effect<Runtime<R>, E, never>` (#3677)
  • Loading branch information
leonitousconforti authored and tim-smart committed Sep 25, 2024
1 parent cac0851 commit 946131f
Show file tree
Hide file tree
Showing 8 changed files with 153 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-beans-jog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

support ManagedRuntime in Effect.provide
5 changes: 5 additions & 0 deletions .changeset/smart-fishes-sit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": minor
---

`ManagedRuntime<R, E>` is subtype of `Effect<Runtime<R>, E, never>`
11 changes: 9 additions & 2 deletions packages/effect/dtslint/Unify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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>
Expand All @@ -125,7 +130,7 @@ export type ResourceUnify = Unify.Unify<
| Resource.Resource<any, any>
>

// $ExpectType 0 | Option<string | number> | 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<string | number> | 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">
Expand Down Expand Up @@ -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
>
8 changes: 8 additions & 0 deletions packages/effect/src/Effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -3330,12 +3331,19 @@ export const provide: {
): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E2 | E, RIn | Exclude<R, ROut>>
<R2>(context: Context.Context<R2>): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, R2>>
<R2>(runtime: Runtime.Runtime<R2>): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, R2>>
<E2, R2>(
managedRuntime: ManagedRuntime.ManagedRuntime<R2, E2>
): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E | E2, Exclude<R, R2>>
<A, E, R, ROut, E2, RIn>(
self: Effect<A, E, R>,
layer: Layer.Layer<ROut, E2, RIn>
): Effect<A, E | E2, RIn | Exclude<R, ROut>>
<A, E, R, R2>(self: Effect<A, E, R>, context: Context.Context<R2>): Effect<A, E, Exclude<R, R2>>
<A, E, R, R2>(self: Effect<A, E, R>, runtime: Runtime.Runtime<R2>): Effect<A, E, Exclude<R, R2>>
<A, E, E2, R, R2>(
self: Effect<A, E, R>,
runtime: ManagedRuntime.ManagedRuntime<R2, E2>
): Effect<A, E | E2, Exclude<R, R2>>
} = layer.effect_provide

/**
Expand Down
47 changes: 45 additions & 2 deletions packages/effect/src/ManagedRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown, unknown> = internal.isManagedRuntime

/**
* @since 3.4.0
Expand All @@ -29,7 +51,8 @@ export declare namespace ManagedRuntime {
* @since 2.0.0
* @category models
*/
export interface ManagedRuntime<in R, out ER> extends Pipeable {
export interface ManagedRuntime<in R, out ER> extends Effect.Effect<Runtime.Runtime<R>, ER> {
readonly [TypeId]: TypeId
readonly memoMap: Layer.MemoMap
readonly runtimeEffect: Effect.Effect<Runtime.Runtime<R>, ER>
readonly runtime: () => Promise<Runtime.Runtime<R>>
Expand Down Expand Up @@ -103,6 +126,26 @@ export interface ManagedRuntime<in R, out ER> extends Pipeable {
* Dispose of the resources associated with the runtime.
*/
readonly disposeEffect: Effect.Effect<void, never, never>

readonly [Unify.typeSymbol]?: unknown
readonly [Unify.unifySymbol]?: ManagedRuntimeUnify<this>
readonly [Unify.ignoreSymbol]?: ManagedRuntimeUnifyIgnore
}

/**
* @category models
* @since 3.9.0
*/
export interface ManagedRuntimeUnify<A extends { [Unify.typeSymbol]?: any }> extends Effect.EffectUnify<A> {
ManagedRuntime?: () => Extract<A[Unify.typeSymbol], ManagedRuntime<any, any>>
}

/**
* @category models
* @since 3.9.0
*/
export interface ManagedRuntimeUnifyIgnore extends Effect.EffectUnifyIgnore {
Effect?: true
}

/**
Expand Down
36 changes: 29 additions & 7 deletions packages/effect/src/internal/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -1288,6 +1289,8 @@ const provideSomeRuntime = dual<
)
})

const ManagedRuntimeTypeId: ManagedRuntime.TypeId = Symbol.for("effect/ManagedRuntime") as ManagedRuntime.TypeId

/** @internal */
export const effect_provide = dual<
{
Expand All @@ -1300,6 +1303,9 @@ export const effect_provide = dual<
<R2>(
runtime: Runtime.Runtime<R2>
): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, R2>>
<E2, R2>(
managedRuntime: ManagedRuntime.ManagedRuntime<R2, E2>
): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, E | E2, Exclude<R, R2>>
},
{
<A, E, R, ROut, E2, RIn>(
Expand All @@ -1314,16 +1320,32 @@ export const effect_provide = dual<
self: Effect.Effect<A, E, R>,
runtime: Runtime.Runtime<R2>
): Effect.Effect<A, E, Exclude<R, R2>>
<A, E, E2, R, R2>(
self: Effect.Effect<A, E, R>,
managedRuntime: ManagedRuntime.ManagedRuntime<R2, E2>
): Effect.Effect<A, E | E2, Exclude<R, R2>>
}
>(
2,
<A, E, R, ROut>(
self: Effect.Effect<A, E, R>,
source: Layer.Layer<ROut, any, any> | Context.Context<ROut> | Runtime.Runtime<ROut>
): Effect.Effect<any, any, Exclude<R, ROut>> =>
isLayer(source)
? provideSomeLayer(self, source as Layer.Layer<ROut, any, any>)
: Context.isContext(source)
? core.provideSomeContext(self, source)
: provideSomeRuntime(self, source as Runtime.Runtime<ROut>)
source:
| Layer.Layer<ROut, any, any>
| Context.Context<ROut>
| Runtime.Runtime<ROut>
| ManagedRuntime.ManagedRuntime<ROut, any>
): Effect.Effect<any, any, Exclude<R, ROut>> => {
if (isLayer(source)) {
return provideSomeLayer(self, source as Layer.Layer<ROut, any, any>)
} else if (Context.isContext(source)) {
return core.provideSomeContext(self, source)
} else if (ManagedRuntimeTypeId in source) {
return core.flatMap(
(source as ManagedRuntime.ManagedRuntime<ROut, any>).runtimeEffect,
(rt) => provideSomeRuntime(self, rt)
)
} else {
return provideSomeRuntime(self, source as Runtime.Runtime<ROut>)
}
}
)
60 changes: 38 additions & 22 deletions packages/effect/src/internal/managedRuntime.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -12,11 +14,17 @@ import * as fiberRuntime from "./fiberRuntime.js"
import * as internalLayer from "./layer.js"
import * as internalRuntime from "./runtime.js"

interface ManagedRuntimeImpl<R, E> extends ManagedRuntime<R, E> {
interface ManagedRuntimeImpl<R, E> extends M.ManagedRuntime<R, E> {
readonly scope: Scope.CloseableScope
cachedRuntime: Runtime.Runtime<R> | undefined
}

/** @internal */
export const TypeId: M.TypeId = Symbol.for("effect/ManagedRuntime") as M.TypeId

/** @internal */
export const isManagedRuntime = (u: unknown): u is M.ManagedRuntime<unknown, unknown> => hasProperty(u, TypeId)

function provide<R, ER, A, E>(
managed: ManagedRuntimeImpl<R, ER>,
effect: Effect.Effect<A, E, R>
Expand All @@ -32,34 +40,42 @@ function provide<R, ER, A, E>(
)
}

const ManagedRuntimeProto = {
...Effectable.CommitPrototype,
[TypeId]: TypeId,
pipe() {
return pipeArguments(this, arguments)
},
commit(this: ManagedRuntimeImpl<unknown, unknown>) {
return this.runtimeEffect
}
}

/** @internal */
export const make = <R, ER>(
layer: Layer.Layer<R, ER, never>,
memoMap?: Layer.MemoMap
): ManagedRuntime<R, ER> => {
): M.ManagedRuntime<R, ER> => {
memoMap = memoMap ?? internalLayer.unsafeMakeMemoMap()
const scope = internalRuntime.unsafeRunSyncEffect(fiberRuntime.scopeMake())
const self: ManagedRuntimeImpl<R, ER> = {
const runtimeEffect = internalRuntime.unsafeRunSyncEffect(
effect.memoize(
core.tap(
Scope.extend(
internalLayer.toRuntimeWithMemoMap(layer, memoMap),
scope
),
(rt) => {
self.cachedRuntime = rt
}
)
)
)
const self: ManagedRuntimeImpl<R, ER> = 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) :
Expand Down Expand Up @@ -110,6 +126,6 @@ export const make = <R, ER>(
internalRuntime.unsafeRunPromiseEffect(provide(self, effect), options) :
internalRuntime.unsafeRunPromise(self.cachedRuntime)(effect, options)
}
}
})
return self
}
14 changes: 14 additions & 0 deletions packages/effect/test/ManagedRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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>("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")
})
)
})

0 comments on commit 946131f

Please sign in to comment.