import { - type EffectionRuntime,
- EffectionRuntimeType,
- type EffectRuntime,
- makeEffectionRuntime,
- makeEffectRuntime
} from "@effectionx/effect-ts"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:
| Feature | Effect | Effection |
|---|---|---|
| Concurrency Model | Fiber-based with supervision | Structured concurrency with scopes |
| Error Handling | Type-safe errors in signature | JavaScript throw/catch |
| Dependencies | Context/Layer system | Context API |
| Syntax | Generator or pipe-based | Generator-based |
| Resource Management | Scope with finalizers | Automatic 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
| Aspect | Effection Host | Effect Host | |
|---|---|---|---|
| Method | EffectRuntime.run() | EffectRuntime.runExit() | EffectionRuntime.run() |
| Error Handling | Throws JS error | Returns Exit<A, E> | Returns UnknownException |
| Use When | Simple cases | Need typed errors | Using Effection in Effect |
| Cancellation | Effection halt → Effect interrupt | Same | Effect 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'sLayer.merge(),Layer.mergeAll(), orLayer.provide()before passing.
Return Type
Operation<EffectRuntime<R>>
An Operation that yields the EffectRuntime
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 anExit<A, E>that you can inspect to determine success or failure. This preserves Effect's full error model including the Cause.
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
UnknownExceptionin Effect. The Effection scope is automatically cleaned up when the Effect completes or is interrupted.
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