Skip to content

Migration Assistant

The migration assistant scans your TypeScript files for patterns that have idiomatic Effect equivalents. It helps teams plan and execute a gradual migration to Effect by identifying exactly where imperative or Promise-based code could be replaced.

Terminal window
npx effect-analyze ./src/legacy-service.ts --format migration
Terminal window
npx effect-analyze ./src --format migration

This scans all TypeScript files in the directory and produces a consolidated report of migration opportunities.

The migration assistant detects the following patterns and suggests their Effect equivalents:

PatternSuggestion
try/catchEffect.try or Effect.tryPromise with catch handler
Promise.allEffect.all([...], { concurrency: "unbounded" })
Promise.raceEffect.race(first, second)
setTimeoutEffect.sleep(Duration.millis(n))
setIntervalSchedule.spaced(Duration.millis(n))
setImmediateEffect.sync + queueMicrotask or Effect.async
fetch()HttpClient.request or @effect/platform HttpClient
http.request / https.requestHttpClient.request or @effect/platform HttpClient
new XMLHttpRequest()HttpClient.request or @effect/platform HttpClient
dns.lookup / dns.resolveEffect.promise or dns.promises
fs.exists (callback)Effect.promise or fs.promises.access
new EventEmitter()PubSub.bounded<T>() or PubSub.unbounded<T>()
new Worker()Worker.make or @effect/platform Worker
Class-based DI (*Service, *Repository, *Client)Context.Tag<T>() + Layer.effect or Layer.succeed

The migration report includes:

  • File path and line number for each opportunity
  • Pattern name - what was detected
  • Suggestion - the Effect equivalent
  • Code snippet - the first 80 characters of the matching code

Example output:

## Migration Opportunities
Found 7 opportunities across 3 files.
### src/legacy-service.ts
| Line | Pattern | Suggestion |
|------|---------|------------|
| 12 | try/catch | Effect.try or Effect.tryPromise with catch handler |
| 28 | Promise.all | Effect.all([...], { concurrency: "unbounded" }) |
| 45 | fetch() | HttpClient.request or @effect/platform HttpClient |
### src/utils/timer.ts
| Line | Pattern | Suggestion |
|------|---------|------------|
| 5 | setTimeout | Effect.sleep(Duration.millis(n)) |
| 18 | setInterval | Schedule.spaced(Duration.millis(n)) |
import { findMigrationOpportunities } from "effect-analyzer"
const opportunities = findMigrationOpportunities("./src/legacy-service.ts")
for (const opp of opportunities) {
console.log(`${opp.filePath}:${opp.line} - ${opp.pattern}${opp.suggestion}`)
}
import { findMigrationOpportunitiesInProject, formatMigrationReport } from "effect-analyzer"
const report = findMigrationOpportunitiesInProject("./src")
console.log(`Found ${report.opportunities.length} opportunities across ${report.fileCount} files`)
const markdown = formatMigrationReport(report)
console.log(markdown)

Pass a source string directly for testing or integration:

import { findMigrationOpportunities } from "effect-analyzer"
const opportunities = findMigrationOpportunities("virtual.ts", `
async function fetchUser(id: string) {
try {
const response = await fetch(\`/api/users/\${id}\`)
return response.json()
} catch (error) {
console.error(error)
throw error
}
}
`)
// Detects: try/catch, fetch()