Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Effect-TS/tsgo/llms.txt

Use this file to discover all available pages before exploring further.

The Effect Language Service exposes refactor actions through your editor’s refactor menu (typically Ctrl+Shift+R / Cmd+Shift+R or right-click → Refactor…). Each refactor inspects the AST at the cursor, validates the surrounding context, and rewrites the code in place.
All refactors work in both Effect V3 and V4 unless noted otherwise.

Async/await conversions

These refactors operate on any async function declaration, function expression, or arrow function. Place your cursor anywhere inside the function and trigger the refactor menu.

asyncAwaitToFn

Converts an async/await function to Effect.fn, replacing await expr with yield* Effect.promise(() => expr) and wrapping the body in a generator. The function name is preserved as the trace string passed to Effect.fn. Support: V3 ✓ · V4 ✓
// Before
async function fetchUser(id: string) {
  const res = await fetch(`/users/${id}`)
  return res.json()
}

// After
const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
  const res = yield* Effect.promise(() => fetch(`/users/${id}`))
  return yield* Effect.promise(() => res.json())
})

asyncAwaitToFnTryPromise

Like asyncAwaitToFn, but wraps each awaited expression in Effect.tryPromise and generates a typed error ADT (FetchError, etc.) for each catch site, giving you a typed error channel instead of UnknownException. Support: V3 ✓ · V4 ✓
// Before
async function fetchUser(id: string) {
  const res = await fetch(`/users/${id}`)
  return res.json()
}

// After
class FetchUserError {
  readonly _tag = "FetchUserError"
  constructor(readonly cause: unknown) {}
}

const fetchUser = Effect.fn("fetchUser")(function* (id: string) {
  const res = yield* Effect.tryPromise({
    try: () => fetch(`/users/${id}`),
    catch: (cause) => new FetchUserError(cause)
  })
  return yield* Effect.tryPromise({
    try: () => res.json(),
    catch: (cause) => new FetchUserError(cause)
  })
})

asyncAwaitToGen

Converts an async/await function to Effect.gen, replacing await expr with yield* Effect.promise(() => expr). Unlike asyncAwaitToFn, the result is wrapped in Effect.gen rather than Effect.fn, so it is not a lazily-constructed pipeline. Support: V3 ✓ · V4 ✓
// Before
async function loadConfig() {
  const text = await readFile("config.json", "utf8")
  return JSON.parse(text)
}

// After
function loadConfig() {
  return Effect.gen(function* () {
    const text = yield* Effect.promise(() => readFile("config.json", "utf8"))
    return JSON.parse(text)
  })
}

asyncAwaitToGenTryPromise

Like asyncAwaitToGen, but wraps each awaited expression in Effect.tryPromise with a generated typed error class. Support: V3 ✓ · V4 ✓
// Before
async function loadConfig() {
  const text = await readFile("config.json", "utf8")
  return JSON.parse(text)
}

// After
class LoadConfigError {
  readonly _tag = "LoadConfigError"
  constructor(readonly cause: unknown) {}
}

function loadConfig() {
  return Effect.gen(function* () {
    const text = yield* Effect.tryPromise({
      try: () => readFile("config.json", "utf8"),
      catch: (cause) => new LoadConfigError(cause)
    })
    return JSON.parse(text)
  })
}

Generator refactors

effectGenToFn

Converts an Effect.gen(function*() { ... }) expression to Effect.fn("name")(function*() { ... }), promoting a one-shot generator into a reusable, traced pipeline function. Support: V3 ✓ · V4 ✓
// Before
const program = Effect.gen(function* () {
  const config = yield* Config
  return config.port
})

// After
const program = Effect.fn("program")(function* () {
  const config = yield* Config
  return config.port
})

wrapWithEffectGen

Wraps the expression under the cursor in an Effect.gen(function*() { return <expr> }) block, making it straightforward to add yield* statements around it. Support: V3 ✓ · V4 ✓
// Before
const value = computeResult()

// After
const value = Effect.gen(function* () {
  return computeResult()
})

removeUnnecessaryEffectGen

Removes an Effect.gen wrapper when the generator body contains a single return statement with no yield* expressions, inlining the returned value directly. Support: V3 ✓ · V4 ✓
// Before
const value = Effect.gen(function* () {
  return Effect.succeed(42)
})

// After
const value = Effect.succeed(42)

Function style

functionToArrow

Converts a function declaration to an equivalent const arrow function, preserving modifiers (e.g., export), type parameters, and parameters. Support: V3 ✓ · V4 ✓
// Before
export function greet(name: string): string {
  return `Hello, ${name}`
}

// After
export const greet = (name: string): string => `Hello, ${name}`

toggleLazyConst

Toggles a const variable between a lazy thunk () => value and an eager value. Useful for deferring expensive computations or removing unnecessary laziness. Support: V3 ✓ · V4 ✓
// Before (eager)
const layer = makeLayer()

// After (lazy)
const layer = () => makeLayer()

togglePipeStyle

Toggles between the pipe(subject, f1, f2) free-function style and the subject.pipe(f1, f2) method-call style. The refactor detects which form is at the cursor and offers the opposite. Support: V3 ✓ · V4 ✓
// Before
const result = pipe(
  Effect.succeed(1),
  Effect.map((n) => n + 1),
  Effect.map((n) => n * 2)
)

// After
const result = Effect.succeed(1).pipe(
  Effect.map((n) => n + 1),
  Effect.map((n) => n * 2)
)

toggleReturnTypeAnnotation

Adds or removes an explicit return type annotation on a function. When adding, it infers the type from the type checker and inserts it. Support: V3 ✓ · V4 ✓
// Before (no annotation)
const getPort = (config: Config) => config.port

// After (with annotation)
const getPort = (config: Config): number => config.port

toggleTypeAnnotation

Adds or removes an explicit type annotation on a const variable declaration. When adding, it infers the type from the type checker. Support: V3 ✓ · V4 ✓
// Before (no annotation)
const port = config.port

// After (with annotation)
const port: number = config.port

pipeableToDatafirst

Converts pipeable-style calls (where the subject is passed as the last argument) to data-first style (where the subject is the first argument). Operates on the call expression at the cursor. Support: V3 ✓ · V4 ✓
// Before (pipeable)
const result = Effect.map(effect, (n) => n + 1)

// After (data-first via pipe)
const result = effect.pipe(Effect.map((n) => n + 1))

wrapWithPipe

Wraps the current selection in a pipe(...) call. Select any expression, then apply this refactor to make it the first argument of a new pipe invocation. Support: V3 ✗ · V4 ✓
// Before (selection: Effect.succeed(42))
const result = Effect.succeed(42)

// After
const result = pipe(Effect.succeed(42))

missedPipeableOpportunity (quick fix)

Triggered as a quick fix alongside the missedPipeableOpportunity diagnostic. Rewrites nested function calls into a single pipe call or .pipe chain, depending on your style preference. Support: V3 ✓ · V4 ✓
// Before (nested calls triggering diagnostic)
const result = Effect.map(Effect.map(Effect.succeed(1), (n) => n + 1), (n) => n * 2)

// After
const result = Effect.succeed(1).pipe(
  Effect.map((n) => n + 1),
  Effect.map((n) => n * 2)
)

Layer composition

layerMagic

Automatically composes a set of layers into a correctly ordered pipeline of Layer.provide, Layer.merge, and Layer.provideMerge calls. This refactor has two phases:
1

Prepare

Place the cursor on a layer expression and apply Prepare layers for automatic composition. The refactor flattens all leaf layers into an array literal and wraps it with a cast: ([L1, L2, ...] as any) as Layer.Layer<TargetService>. The union of output types is computed and annotated on the cast.
2

Build

With the cursor on the prepared cast expression, apply Compose layers automatically with target output services. The refactor reads the dependency graph, determines the correct composition order, and rewrites the expression to L1.pipe(Layer.provideMerge(L2), Layer.provide(L3), ...). Any output types that could not be satisfied are listed in a trailing comment.
Support: V3 ✓ · V4 ✓
// Prepare step — mark layers for composition
const AppLayer = ([DatabaseLayer, CacheLayer, HttpLayer] as any) as Layer.Layer<App>

// Build step — auto-composed result
const AppLayer = DatabaseLayer.pipe(
  Layer.provideMerge(CacheLayer),
  Layer.provide(HttpLayer)
)
Use the Layer Graph hover (see Layer Graph) to visualise the dependency structure before running the build step.

Schema generation

makeSchemaOpaque

Converts a schema variable declaration into an opaque type pattern. Renames the original variable to Name_ and inserts exported opaque interface/type aliases for Name, NameEncoded, and NameContext (V3) or NameDecodingServices / NameEncodingServices (V4), plus a re-exported const Name with a full type annotation. Support: V3 ✓ · V4 ✓
// Before
const UserId = Schema.String.pipe(Schema.brand("UserId"))

// After (V3 output)
const UserId_ = Schema.String.pipe(Schema.brand("UserId"))
export interface UserId extends Schema.Schema.Type<typeof UserId_> {}
export interface UserIdEncoded extends Schema.Schema.Encoded<typeof UserId_> {}
export type UserIdContext = Schema.Schema.Context<typeof UserId_>
export const UserId: Schema.Schema<UserId, UserIdEncoded, UserIdContext> = UserId_

makeSchemaOpaqueWithNs

Like makeSchemaOpaque, but additionally wraps the generated types inside a namespace matching the variable name, giving you UserId.Type, UserId.Encoded, etc. Support: V3 ✓ · V4 ✓
// Before
const UserId = Schema.String.pipe(Schema.brand("UserId"))

// After
export namespace UserId {
  export interface Type extends Schema.Schema.Type<typeof UserId_> {}
  export interface Encoded extends Schema.Schema.Encoded<typeof UserId_> {}
  export type Context = Schema.Schema.Context<typeof UserId_>
}
const UserId_ = Schema.String.pipe(Schema.brand("UserId"))
export const UserId: Schema.Schema<UserId.Type, UserId.Encoded, UserId.Context> = UserId_

structuralTypeToSchema

Generates a recursive Effect.Schema from the structural shape of a type alias or interface, following all referenced types transitively. Unlike typeToEffectSchema, this uses the resolved type information from the checker rather than the syntactic form of the declaration. Support: V3 ✓ · V4 ✓
// Before — type alias with nested references
type Address = { street: string; city: string }
type User = { name: string; age: number; address: Address }

// After — inserted above the type alias
const Address = Schema.Struct({
  street: Schema.String,
  city: Schema.String
})

const User = Schema.Struct({
  name: Schema.String,
  age: Schema.Number,
  address: Address
})

type Address = { street: string; city: string }
type User = { name: string; age: number; address: Address }

typeToEffectSchema

Generates an Effect.Schema declaration from a type alias or interface declaration. The cursor must be on the type name. Inserts the new schema constant immediately before the type declaration. Support: V3 ✓ · V4 ✓
// Before
type User = {
  name: string
  age: number
}

// After
const User = Schema.Struct({
  name: Schema.String,
  age: Schema.Number
})

type User = {
  name: string
  age: number
}

typeToEffectSchemaClass

Generates a Schema.Class declaration from a type alias or interface. The class form gives you a nominal type with a class-based constructor, which is useful for tagged structs and error types. Support: V3 ✓ · V4 ✓
// Before
type User = {
  name: string
  age: number
}

// After
class User extends Schema.Class<User>("User")({
  name: Schema.String,
  age: Schema.Number
}) {}

type User = {
  name: string
  age: number
}

Service accessors

writeTagClassAccessors

Generates static override accessor methods on an Effect.Service or Effect.Tag class for service methods that have generic type parameters or multiple overloads. Each generated accessor delegates to the service method via Effect.andThen, providing a convenient static entry point with the full type signature. Support: V3 ✓ · V4 ✗ (not applicable in V4’s ServiceMap model)
// Before
class Logger extends Effect.Service<Logger>()("Logger", {
  accessors: true,
  effect: Effect.gen(function* () {
    return {
      log: <A>(value: A): Effect.Effect<void> => Console.log(value)
    }
  })
}) {}

// After — accessor inserted inside the class body
class Logger extends Effect.Service<Logger>()("Logger", {
  accessors: true,
  effect: Effect.gen(function* () {
    return {
      log: <A>(value: A): Effect.Effect<void> => Console.log(value)
    }
  })
}) {
  static override log: <A>(value: A) => Effect.Effect<void, never, Logger> =
    (...args) => Effect.andThen(Logger, (_) => _.log(...args))
}