Effection Logo
@effectionx/effect-tsv0.1.0thefrontside/effectionx
NPM Badge with published version

Effect-TS

Bidirectional interop between Effect-TS and Effection.

Why?

Effect and Effection are both powerful libraries for managing side effects in TypeScript, but they have different philosophies and strengths:

FeatureEffectEffection
Concurrency ModelFiber-based with supervisionStructured concurrency with scopes
Error HandlingType-safe errors in signatureJavaScript throw/catch
DependenciesContext/Layer systemContext API
SyntaxGenerator or pipe-basedGenerator-based
Resource ManagementScope with finalizersAutomatic cleanup on scope exit

This package lets you use both together:

  • Use Effect inside Effection when you want Effect's type-safe error handling or need to use Effect-based libraries within Effection's structured concurrency
  • Use Effection inside Effect when you want Effection's simple generator syntax or need to use Effection-based libraries within an Effect application

Installation

npm install @effectionx/effect-ts

Peer dependencies: Both effect (^3) and effection (^3 || ^4) must be installed.

Effection Host - Effect Guest

Use makeEffectRuntime() to run Effect programs inside Effection operations.

Basic Usage

import { main } from "effection";
import { Effect } from "effect";
import { makeEffectRuntime } from "@effectionx/effect-ts";

await main(function* () {
  // Create the Effect runtime (automatically disposed when scope ends)
  const runtime = yield* makeEffectRuntime();

  // Run Effect programs
  const result = yield* runtime.run(
    Effect.succeed(42).pipe(Effect.map(n => n * 2))
  );

  console.log(result); // 84
});

Error Handling

Effect failures are thrown as JavaScript errors when using run():

import { main } from "effection";
import { Effect } from "effect";
import { makeEffectRuntime } from "@effectionx/effect-ts";

await main(function* () {
  const runtime = yield* makeEffectRuntime();

  try {
    yield* runtime.run(Effect.fail(new Error("boom")));
  } catch (error) {
    console.log(error.message); // "boom"
  }
});

For type-safe error handling, use runExit() which returns an Exit<A, E>:

import { main } from "effection";
import { Effect, Exit } from "effect";
import { makeEffectRuntime } from "@effectionx/effect-ts";

await main(function* () {
  const runtime = yield* makeEffectRuntime();

  const exit = yield* runtime.runExit(Effect.fail(new Error("boom")));

  if (Exit.isFailure(exit)) {
    // Access the full Cause<E> with error details
    console.log(exit.cause);
  } else {
    // Access the success value
    console.log(exit.value);
  }
});

With Effect Services

You can provide an Effect Layer to pre-configure services:

import { main } from "effection";
import { Effect, Context, Layer } from "effect";
import { makeEffectRuntime } from "@effectionx/effect-ts";

// Define a service
class Logger extends Context.Tag("Logger")<Logger, {
  log: (msg: string) => Effect.Effect<void>
}>() {}

const LoggerLive = Layer.succeed(Logger, {
  log: (msg) => Effect.sync(() => console.log(msg))
});

await main(function* () {
  // Provide layer to the runtime
  const runtime = yield* makeEffectRuntime(LoggerLive);

  // Effects can now use Logger without explicit provide
  yield* runtime.run(
    Effect.gen(function* () {
      const logger = yield* Logger;
      yield* logger.log("Hello!");
    })
  );
});

Compose multiple layers using Effect's primitives:

const AppLayer = Layer.mergeAll(DatabaseLive, LoggerLive, CacheLive);
const runtime = yield* makeEffectRuntime(AppLayer);

Cancellation

When an Effection scope is halted, any running Effect programs are interrupted:

import { main, spawn, sleep } from "effection";
import { Effect } from "effect";
import { makeEffectRuntime } from "@effectionx/effect-ts";

await main(function* () {
  const runtime = yield* makeEffectRuntime();

  const task = yield* spawn(function* () {
    yield* runtime.run(
      Effect.gen(function* () {
        yield* Effect.addFinalizer(() => Effect.log("Effect interrupted!"));
        yield* Effect.sleep("10 seconds");
      }).pipe(Effect.scoped)
    );
  });

  yield* sleep(100);
  // Task is automatically halted when main scope ends
  // Effect finalizer runs: "Effect interrupted!"
});

Effect Host - Effection Guest

Use makeEffectionRuntime() to run Effection operations inside Effect programs.

Basic Usage

import { Effect } from "effect";
import { sleep } from "effection";
import { makeEffectionRuntime, EffectionRuntime } from "@effectionx/effect-ts";

const program = Effect.gen(function* () {
  const runtime = yield* EffectionRuntime;

  const result = yield* runtime.run(function* () {
    yield* sleep(100);
    return "hello from effection";
  });

  return result.toUpperCase();
});

const result = await Effect.runPromise(
  program.pipe(
    Effect.provide(makeEffectionRuntime()),
    Effect.scoped
  )
);

console.log(result); // "HELLO FROM EFFECTION"

Error Handling

Errors thrown in Effection operations become UnknownException in Effect:

import { Effect, Exit } from "effect";
import { makeEffectionRuntime, EffectionRuntime } from "@effectionx/effect-ts";

const program = Effect.gen(function* () {
  const runtime = yield* EffectionRuntime;

  return yield* runtime.run(function* () {
    throw new Error("boom");
  });
});

const exit = await Effect.runPromiseExit(
  program.pipe(
    Effect.provide(makeEffectionRuntime()),
    Effect.scoped
  )
);

// exit is Exit.Failure with UnknownException containing the error
if (Exit.isFailure(exit)) {
  console.log("Failed:", exit.cause);
}

Cancellation

When the Effect scope ends or is interrupted, the Effection scope is closed:

import { Effect, Fiber } from "effect";
import { suspend } from "effection";
import { makeEffectionRuntime, EffectionRuntime } from "@effectionx/effect-ts";

const program = Effect.gen(function* () {
  const runtime = yield* EffectionRuntime;

  const fiber = yield* runtime.run(function* () {
    try {
      yield* suspend();
    } finally {
      console.log("Effection cleanup!");
    }
  }).pipe(Effect.fork);

  yield* Effect.sleep("100 millis");
  yield* Fiber.interrupt(fiber);
  // Logs: "Effection cleanup!"
});

await Effect.runPromise(
  program.pipe(
    Effect.provide(makeEffectionRuntime()),
    Effect.scoped
  )
);

Comparison

AspectEffection HostEffect Host
MethodEffectRuntime.run()EffectRuntime.runExit()EffectionRuntime.run()
Error HandlingThrows JS errorReturns Exit<A, E>Returns UnknownException
Use WhenSimple casesNeed typed errorsUsing Effection in Effect
CancellationEffection halt → Effect interruptSameEffect interrupt → Effection halt

API Reference

function makeEffectRuntime<R = never>(layer?: Layer.Layer<R, never, never>): Operation<EffectRuntime<R>>

Create an EffectRuntime resource that manages an Effect ManagedRuntime.

The ManagedRuntime is automatically disposed when the Effection scope ends, ensuring proper cleanup of Effect resources.

Examples

Example 1

Basic usage

import { run } from "effection";
import { Effect } from "effect";
import { makeEffectRuntime } from "@effectionx/effect";

await run(function* () {
  const runtime = yield* makeEffectRuntime();
  const result = yield* runtime.run(Effect.succeed(42));
  console.log(result); // 42
});

Example 2

With services

import { Layer, Context, Effect } from "effect";

class Logger extends Context.Tag("Logger")<Logger, { log: (msg: string) => Effect.Effect<void> }>() {}
const LoggerLive = Layer.succeed(Logger, { log: (msg) => Effect.log(msg) });

await run(function* () {
  const runtime = yield* makeEffectRuntime(LoggerLive);
  yield* runtime.run(Effect.gen(function* () {
    const logger = yield* Logger;
    yield* logger.log("Hello!");
  }));
});

Example 3

Composing multiple layers

const AppLayer = Layer.mergeAll(DatabaseLive, LoggerLive, CacheLive);
const runtime = yield* makeEffectRuntime(AppLayer);

Type Parameters

R = never

Parameters

layeroptional: Layer<R, never, never>

  • Optional Effect Layer to provide services. Defaults to Layer.empty. Users can compose multiple layers using Effect's Layer.merge(), Layer.mergeAll(), or Layer.provide() before passing.

Return Type

Operation<EffectRuntime<R>>

An Operation that yields the EffectRuntime

interface EffectRuntime<R = never>

A runtime for executing Effect programs inside Effection operations.

Type Parameters

R = never

  • The services/context provided by this runtime (from the layer)

Methods

run
<A, E>(effect: Effect<A, E, R>): Operation<A>

Run an Effect program and return its result as an Effection Operation.

Effect failures will be thrown as JavaScript errors.

runExit
<A, E>(effect: Effect<A, E, R>): Operation<Exit<A, E>>

Run an Effect program and return its Exit (success or failure).

Unlike run(), this does not throw on failure. Instead, it returns an Exit<A, E> that you can inspect to determine success or failure. This preserves Effect's full error model including the Cause.

interface EffectionRuntime

A runtime for executing Effection operations inside Effect programs.

Methods

run
<T>(operation: () => Operation<T>): Effect<T, UnknownException>

Run an Effection operation and return its result as an Effect.

Errors thrown in the operation become UnknownException in Effect. The Effection scope is automatically cleaned up when the Effect completes or is interrupted.

const EffectionRuntime:

Effect Context Tag for accessing the EffectionRuntime.

Examples

Example 1
const program = Effect.gen(function* () {
  const runtime = yield* EffectionRuntime;
  // use runtime.run(...)
});

function makeEffectionRuntime(parent?: Scope): Layer.Layer<EffectionRuntime>

Create an Effect Layer that provides an EffectionRuntime.

The Effection scope is automatically closed when the Effect scope ends, ensuring proper cleanup of Effection resources.

Examples

Example 1

Basic usage

import { Effect } from "effect";
import { sleep } from "effection";
import { makeEffectionRuntime, EffectionRuntime } from "@effectionx/effect";

const program = Effect.gen(function* () {
  const runtime = yield* EffectionRuntime;
  const result = yield* runtime.run(function* () {
    yield* sleep(100);
    return "hello from effection";
  });
  return result;
});

await Effect.runPromise(
  program.pipe(
    Effect.provide(makeEffectionRuntime()),
    Effect.scoped
  )
);

Example 2

With parent scope (to inherit Effection contexts)

import { Effect } from "effect";
import { useScope } from "effection";
import { makeEffectionRuntime, EffectionRuntime } from "@effectionx/effect";

function* myOperation() {
  const scope = yield* useScope();
  const result = yield* call(() =>
    Effect.runPromise(
      Effect.gen(function* () {
        const runtime = yield* EffectionRuntime;
        return yield* runtime.run(function* () {
          // Can access Effection contexts from parent scope
          return "hello";
        });
      }).pipe(Effect.provide(makeEffectionRuntime(scope)), Effect.scoped)
    )
  );
  return result;
}

Parameters

parentoptional: Scope

  • Optional parent Effection scope. If provided, the runtime's scope will inherit all contexts from the parent scope.

Return Type

Layer<EffectionRuntime>

An Effect Layer providing EffectionRuntime