Skip to content

Commit

Permalink
switch to module
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart committed Mar 7, 2024
1 parent 3cdff6d commit 07ea1b8
Show file tree
Hide file tree
Showing 8 changed files with 381 additions and 262 deletions.
18 changes: 8 additions & 10 deletions .changeset/famous-mugs-attack.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
"effect": patch
---

add Layer.toRunner, to make incremental adoption easier
add ManagedRuntime module, to make incremental adoption easier

You can use a Layer.toRunner to build an Effect runner that can use the
You can use a ManagedRuntime to run Effect's that can use the
dependencies from the given Layer. For example:

```ts
import type { Effect } from "effect";
import { Console, Context, Layer } from "effect";
import { Console, Effect, Layer, ManagedRuntime } from "effect";

class Notifications extends Context.Tag("Notifications")<
class Notifications extends Effect.Tag("Notifications")<
Notifications,
{ readonly notify: (message: string) => Effect.Effect<void> }
>() {
Expand All @@ -21,11 +20,10 @@ class Notifications extends Context.Tag("Notifications")<
}

async function main() {
const runner = Layer.toRunner(Notifications.Live);
await runner.runPromiseService(Notifications, (_) =>
_.notify("Hello world!")
);
await runner.dispose();
const runtime = ManagedRuntime.make(Notifications.Live);
const runPromise = ManagedRuntime.runPromise(runtime);
await runPromise(Notifications.notify("Hello, world!"));
await ManagedRuntime.dispose(runtime);
}

main();
Expand Down
68 changes: 0 additions & 68 deletions packages/effect/src/Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*
* @since 2.0.0
*/
import type * as Fiber from "effect/Fiber"
import type * as Cause from "./Cause.js"
import type * as Clock from "./Clock.js"
import type { ConfigProvider } from "./ConfigProvider.js"
Expand Down Expand Up @@ -1073,70 +1072,3 @@ export const buildWithMemoMap: {
scope: Scope.Scope
): Effect.Effect<Context.Context<ROut>, E, RIn>
} = internal.buildWithMemoMap

// -----------------------------------------------------------------------------
// runner
// -----------------------------------------------------------------------------

/**
* @since 2.0.0
* @category runner
*/
export interface Runner<R, RE> extends AsyncDisposable {
readonly dispose: () => Promise<void>
readonly memoMap: MemoMap
readonly runFork: <E, A>(effect: Effect.Effect<A, E, R>) => Fiber.RuntimeFiber<A, E | RE>
readonly runSync: <E, A>(effect: Effect.Effect<A, E, R>) => A
readonly runSyncExit: <E, A>(effect: Effect.Effect<A, E, R>) => Exit.Exit<A, E | RE>
readonly runPromise: <E, A>(effect: Effect.Effect<A, E, R>) => Promise<A>
readonly runPromiseExit: <E, A>(effect: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, E | RE>>
readonly runPromiseFn: <
F extends (...args: Array<any>) => Effect.Effect<any, any, R>
>(
fn: F
) => (...args: Parameters<F>) => Promise<Effect.Effect.Success<ReturnType<F>>>
readonly runPromiseService: <I extends R, S, A, E>(
tag: Context.Tag<I, S>,
fn: (service: S) => Effect.Effect<A, E, R>
) => Promise<A>
readonly runPromiseServiceFn: <
I extends R,
S,
F extends (...args: Array<any>) => Effect.Effect<any, any, R>
>(
tag: Context.Tag<I, S>,
fn: (service: S) => F
) => (...args: Parameters<F>) => Promise<Effect.Effect.Success<ReturnType<F>>>
}

/**
* Convert a Layer into an Effect runner, than can be used to integrate Effect's with Promise
* based code.
*
* @since 2.0.0
* @category runtime class
* @example
* import type { Effect } from "effect"
* import { Console, Context, Layer } from "effect"
*
* class Notifications extends Context.Tag("Notifications")<
* Notifications,
* {
* readonly notify: (message: string) => Effect.Effect<void>
* }
* >() {
* static Live = Layer.succeed(this, {
* notify: (message) => Console.log(message)
* })
* }
*
* async function main() {
* const runner = Layer.toRunner(Notifications.Live)
* await runner.runPromiseService(Notifications, (_) => _.notify("Hello, world!"))
* await runner.dispose()
* }
*
* main()
*/
export const toRunner: <R, RE>(layer: Layer<R, RE, never>, memoMap?: MemoMap | undefined) => Runner<R, RE> =
internal.toRunner
148 changes: 148 additions & 0 deletions packages/effect/src/ManagedRuntime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/**
* @since 2.0.0
*/
import type * as Effect from "./Effect.js"
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"

/**
* @since 2.0.0
* @category models
*/
export interface ManagedRuntime<in R, out E> extends Pipeable, AsyncDisposable {
readonly memoMap: Layer.MemoMap
readonly runtime: Effect.Effect<Runtime.Runtime<R>, E>
}

/**
* Convert a Layer into an ManagedRuntime, than can be used to run Effect's using
* your services.
*
* @since 2.0.0
* @category runtime class
* @example
* import { Console, Effect, Layer, ManagedRuntime } from "effect"
*
* class Notifications extends Effect.Tag("Notifications")<
* Notifications,
* { readonly notify: (message: string) => Effect.Effect<void> }
* >() {
* static Live = Layer.succeed(this, { notify: (message) => Console.log(message) })
* }
*
* async function main() {
* const runtime = ManagedRuntime.make(Notifications.Live)
* const runPromise = ManagedRuntime.runPromise(runtime)
* await runPromise(Notifications.notify("Hello, world!"))
* await ManagedRuntime.dispose(runtime)
* }
*
* main()
*/
export const make: <R, E>(
layer: Layer.Layer<R, E, never>,
memoMap?: Layer.MemoMap | undefined
) => ManagedRuntime<R, E> = 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: <R, ER>(
runtime: ManagedRuntime<R, ER>
) => <A, E>(self: Effect.Effect<A, E, R>, options?: Runtime.RunForkOptions) => Fiber.RuntimeFiber<A, E | ER> =
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: <R, ER>(
self: ManagedRuntime<R, ER>
) => <A, E>(effect: Effect.Effect<A, E, R>) => Exit.Exit<A, ER | E> = 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: <R, ER>(self: ManagedRuntime<R, ER>) => <A, E>(effect: Effect.Effect<A, E, R>) => 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: <R, ER>(
runtime: ManagedRuntime<R, ER>
) => <A, E>(
effect: Effect.Effect<A, E, R>,
options?: Runtime.RunCallbackOptions<A, E | ER> | undefined
) => Runtime.Cancel<A, E | ER> = 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: <R, ER>(self: ManagedRuntime<R, ER>) => <A, E>(effect: Effect.Effect<A, E, R>) => Promise<A> =
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: <R, ER>(
self: ManagedRuntime<R, ER>
) => <A, E>(effect: Effect.Effect<A, E, R>) => Promise<Exit.Exit<A, ER | E>> = internal.runPromiseExit

/**
* Dispose of the resources associated with the runtime.
*
* @since 2.0.0
* @category finalization
*/
export const dispose: <R, E>(self: ManagedRuntime<R, E>) => Promise<void> = internal.dispose

/**
* Dispose of the resources associated with the runtime.
*
* @since 2.0.0
* @category finalization
*/
export const disposeEffect: <R, E>(self: ManagedRuntime<R, E>) => Effect.Effect<void, never, never> =
internal.disposeEffect
5 changes: 5 additions & 0 deletions packages/effect/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,11 @@ export * as LogSpan from "./LogSpan.js"
*/
export * as Logger from "./Logger.js"

/**
* @since 2.0.0
*/
export * as ManagedRuntime from "./ManagedRuntime.js"

/**
* @since 1.0.0
*/
Expand Down
101 changes: 0 additions & 101 deletions packages/effect/src/internal/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as Context from "../Context.js"
import * as Duration from "../Duration.js"
import type * as Effect from "../Effect.js"
import type * as Exit from "../Exit.js"
import type * as Fiber from "../Fiber.js"
import type { FiberRef } from "../FiberRef.js"
import * as FiberRefsPatch from "../FiberRefsPatch.js"
import type { LazyArg } from "../Function.js"
Expand Down Expand Up @@ -1244,103 +1243,3 @@ export const effect_provide = dual<
? core.provideSomeContext(self, source)
: provideSomeRuntime(self, source as Runtime.Runtime<ROut>)
)

export const toRunner = <R, RE>(
layer: Layer.Layer<R, RE, never>,
memoMap?: Layer.MemoMap
): Layer.Runner<R, RE> => {
memoMap = memoMap ?? unsafeMakeMemoMap()
const scope = runtime.unsafeRunSyncEffect(fiberRuntime.scopeMake())

let runtimeOrEffect: Effect.Effect<Runtime.Runtime<R>, RE> | Runtime.Runtime<R> = runtime.unsafeRunSyncEffect(
effect.memoize(
core.tap(
Scope.extend(
toRuntimeWithMemoMap(layer, memoMap),
scope
),
(rt) => {
runtimeOrEffect = rt
}
)
)
)

function provide<A, E>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E | RE> {
if (!core.isEffect(runtimeOrEffect)) {
return provideSomeRuntime(effect, runtimeOrEffect)
}
return core.flatMap(
runtimeOrEffect,
(rt) =>
core.withFiberRuntime((fiber) => {
fiber.setFiberRefs(rt.fiberRefs)
fiber._runtimeFlags = rt.runtimeFlags
return core.provideContext(effect, rt.context)
})
)
}

function dispose(): Promise<void> {
runtimeOrEffect = core.die("Runner disposed")
return runtime.unsafeRunPromiseEffect(Scope.close(scope, core.exitUnit))
}

function runPromise<E, A>(effect: Effect.Effect<A, E, R>): Promise<A> {
return core.isEffect(runtimeOrEffect)
? runtime.unsafeRunPromiseEffect(provide(effect))
: runtime.unsafeRunPromise(runtimeOrEffect)(effect)
}

return {
memoMap,
[Symbol.asyncDispose]() {
return dispose()
},
dispose,
runFork<E, A>(effect: Effect.Effect<A, E, R>): Fiber.RuntimeFiber<A, E | RE> {
return core.isEffect(runtimeOrEffect)
? runtime.unsafeForkEffect(provide(effect))
: runtime.unsafeFork(runtimeOrEffect)(effect)
},
runSync<A, E>(effect: Effect.Effect<A, E, R>): A {
return core.isEffect(runtimeOrEffect)
? runtime.unsafeRunSyncEffect(provide(effect))
: runtime.unsafeRunSync(runtimeOrEffect)(effect)
},
runSyncExit<A, E>(effect: Effect.Effect<A, E, R>): Exit.Exit<A, E | RE> {
return core.isEffect(runtimeOrEffect)
? runtime.unsafeRunSyncExitEffect(provide(effect))
: runtime.unsafeRunSyncExit(runtimeOrEffect)(effect)
},
runPromise,
runPromiseExit<E, A>(effect: Effect.Effect<A, E, R>): Promise<Exit.Exit<A, E | RE>> {
return core.isEffect(runtimeOrEffect)
? runtime.unsafeRunPromiseExitEffect(provide(effect))
: runtime.unsafeRunPromiseExit(runtimeOrEffect)(effect)
},
runPromiseFn<
F extends (...args: Array<any>) => Effect.Effect<any, any, R>
>(
fn: F
): (...args: Parameters<F>) => Promise<Effect.Effect.Success<ReturnType<F>>> {
return (...args) => runPromise(fn(...args))
},
runPromiseService<I extends R, S, A, E>(
tag: Context.Tag<I, S>,
fn: (service: S) => Effect.Effect<A, E, R>
): Promise<A> {
return runPromise(core.flatMap(tag, (_) => fn(_)))
},
runPromiseServiceFn<
I extends R,
S,
F extends (...args: Array<any>) => Effect.Effect<any, any, R>
>(
tag: Context.Tag<I, S>,
fn: (service: S) => F
): (...args: Parameters<F>) => Promise<Effect.Effect.Success<ReturnType<F>>> {
return (...args) => runPromise(core.flatMap(tag, (_) => fn(_).apply(_, args)))
}
}
}
Loading

0 comments on commit 07ea1b8

Please sign in to comment.