Runtime in Effect
Overview
The Runtime is Effect's execution engine:
- Default Runtime - Built-in, zero configuration
- Custom Runtime - Configure services, context, execution
- ManagedRuntime - Lifecycle-managed custom runtimes
Default Runtime
Effects run via the default runtime:
import { Effect } from "effect";
const program = Effect.succeed(42);
const result = await Effect.runPromise(program);
const syncResult = Effect.runSync(program);
const exit = await Effect.runPromiseExit(program);
Run Methods
| Method | Returns | Throws on Error |
|---|---|---|
Effect.runPromise(e) | Promise<A> | Yes |
Effect.runPromiseExit(e) | Promise<Exit<A, E>> | No |
Effect.runSync(e) | A | Yes |
Effect.runSyncExit(e) | Exit<A, E> | No |
Locally Scoped Configuration
Modify runtime behavior for specific effects:
import { Logger, LogLevel } from "effect";
const program = Effect.gen(function* () {
yield* Effect.log("This appears");
yield* Effect.logDebug("This may not appear");
});
const withDebug = program.pipe(Logger.withMinimumLogLevel(LogLevel.Debug));
Effect.Tag for Services
Create typed service tags for dependency injection:
import { Effect, Context } from "effect";
class Database extends Context.Tag("Database")<
Database,
{
readonly query: (sql: string) => Effect.Effect<unknown[]>;
readonly execute: (sql: string) => Effect.Effect<void>;
}
>() {}
const program = Effect.gen(function* () {
const db = yield* Database;
const users = yield* db.query("SELECT * FROM users");
return users;
});
ManagedRuntime
For applications needing custom runtime configuration:
import { ManagedRuntime, Layer } from "effect";
const AppLive = Layer.mergeAll(DatabaseLive, LoggerLive, ConfigLive);
const runtime = ManagedRuntime.make(AppLive);
const main = async () => {
const result = await runtime.runPromise(program);
console.log(result);
await runtime.dispose();
};
ManagedRuntime Benefits
- Pre-builds layer once
- Reuses services across effect runs
- Proper cleanup with dispose()
- Integration with frameworks
Framework Integration
Express Integration
import express from "express";
import { ManagedRuntime, Layer } from "effect";
const AppLive = Layer.mergeAll(DatabaseLive, AuthLive);
const runtime = ManagedRuntime.make(AppLive);
const app = express();
app.get("/users", async (req, res) => {
const result = await runtime.runPromise(
getUsers().pipe(Effect.catchAll((error) => Effect.succeed({ error: error.message }))),
);
res.json(result);
});
// Cleanup on shutdown
process.on("SIGTERM", () => {
runtime.dispose().then(() => process.exit(0));
});
React Integration
import { ManagedRuntime } from "effect"
import { createContext, useContext } from "react"
// Create runtime context
const RuntimeContext = createContext<ManagedRuntime<AppServices> | null>(null)
// Provider component
export function AppProvider({ children }: { children: React.ReactNode }) {
const [runtime] = useState(() => ManagedRuntime.make(AppLive))
useEffect(() => {
return () => { runtime.dispose() }
}, [])
return (
<RuntimeContext.Provider value={runtime}>
{children}
</RuntimeContext.Provider>
)
}
// Hook to use runtime
export function useRuntime() {
const runtime = useContext(RuntimeContext)
if (!runtime) throw new Error("Runtime not provided")
return runtime
}
// Usage in components
function UserList() {
const runtime = useRuntime()
const [users, setUsers] = useState<User[]>([])
useEffect(() => {
runtime.runPromise(fetchUsers()).then(setUsers)
}, [])
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
}
Runtime Configuration
Custom Execution Context
import { Runtime, FiberRef } from "effect";
const customRuntime = Runtime.defaultRuntime.pipe(Runtime.withFiberRef(FiberRef.currentLogLevel, LogLevel.Debug));
Runtime.runPromise(customRuntime)(program);
Providing Services to Runtime
const runtimeWithServices = Runtime.defaultRuntime.pipe(
Runtime.provideService(Database, databaseImpl),
Runtime.provideService(Logger, loggerImpl),
);
Default Services
Effect provides these services automatically:
import { Clock, Random, Tracer, Console } from "effect";
const program = Effect.gen(function* () {
const now = yield* Clock.currentTimeMillis;
const rand = yield* Random.next;
yield* Console.log("Hello");
});
Overriding Default Services
import { TestClock } from "effect";
const testProgram = program.pipe(Effect.provide(TestClock.layer));
const testWithTime = Effect.gen(function* () {
const fiber = yield* Effect.fork(Effect.sleep("1 hour"));
yield* TestClock.adjust("1 hour");
yield* Fiber.join(fiber);
});
Interruption Handling
const program = Effect.gen(function* () {
const fiber = yield* Effect.fork(longRunningTask);
yield* Fiber.interrupt(fiber);
});
const critical = Effect.uninterruptible(
Effect.gen(function* () {
yield* startTransaction();
yield* doWork();
yield* commitTransaction();
}),
);
Best Practices
- Use ManagedRuntime for apps - Proper lifecycle management
- Provide services via layers - Not runtime modification
- Use Effect.Tag for services - Type-safe dependency injection
- Handle cleanup properly - Always dispose() ManagedRuntime
- Test with TestClock - Deterministic time in tests
Additional Resources
For comprehensive runtime documentation, consult ${CLAUDE_PLUGIN_ROOT}/references/llms-full.txt.
Search for these sections:
- "Introduction to Runtime" for core concepts
- "ManagedRuntime" for managed runtime
- "Effect.Tag" for service tags
- "Integrations" for framework integration