---
name: better-option-adopt
description: Migrate codebase from nullable types and undefined checks to better-option. Use when adopting Option types, converting null/undefined handling to typed optionals, or refactoring existing optional value patterns.
---

# better-option Adoption

Migrate existing optional value handling (null checks, undefined, optional chaining) to typed Option-based handling with better-option.

## When to Use

- Adopting better-option in existing codebase
- Converting `T | null | undefined` to `Option<T>`
- Replacing null checks with Option combinators
- Migrating `array.find()` and similar nullable returns
- Introducing railway-oriented programming for optional values

## Migration Strategy

### 1. Start at Boundaries

Begin migration at data access boundaries (API responses, DB queries, user input) and work inward. Don't attempt full-codebase migration at once.

### 2. Identify Nullable Patterns

Before migrating, categorize nullable patterns in target code:

| Pattern                    | Example                      | Migration Target                    |
| -------------------------- | ---------------------------- | ----------------------------------- |
| Nullable return            | `find()`, `get()`            | `Option<T>` return type             |
| Optional property          | `user?.profile`              | `Option.from()` + `map`             |
| Null coalescing            | `value ?? default`           | `Option.unwrapOr(default)`          |
| Conditional on null        | `if (x != null) { ... }`     | `Option.match` or `isSome`          |
| Chained null checks        | `a?.b?.c?.d`                 | `Option.gen` composition            |

### 3. Migration Order

1. Wrap nullable values with `Option.from()`
2. Convert null checks to Option combinators
3. Chain operations with `.map()` and `.andThen()`
4. Refactor complex flows to generator composition

## Pattern Transformations

### Nullable to Option.from

```typescript
// BEFORE
function findUser(id: string): User | null {
  return users.find((u) => u.id === id) ?? null;
}
// Caller: if (user !== null) { ... }

// AFTER
function findUser(id: string): Option<User> {
  return Option.from(users.find((u) => u.id === id));
}
// Caller: option.match({ some: (u) => ..., none: () => ... })
```

### Null Check to Option.match

```typescript
// BEFORE
const user = findUser(id);
if (user !== null) {
  console.log(user.name);
} else {
  console.log("Not found");
}

// AFTER
findUser(id).match({
  some: (user) => console.log(user.name),
  none: () => console.log("Not found"),
});
```

### Optional Chaining to Option.map

```typescript
// BEFORE
const name = user?.profile?.displayName ?? "Anonymous";

// AFTER
const name = Option.from(user)
  .andThen((u) => Option.from(u.profile))
  .map((p) => p.displayName)
  .unwrapOr("Anonymous");
```

### Nested Null Checks to Generator

```typescript
// BEFORE
function getDisplayName(userId: string): string | null {
  const user = findUser(userId);
  if (!user) return null;
  
  const profile = getProfile(user.profileId);
  if (!profile) return null;
  
  const settings = getSettings(profile.settingsId);
  if (!settings) return null;
  
  return settings.displayName;
}

// AFTER
function getDisplayName(userId: string): Option<string> {
  return Option.gen(function* () {
    const user = yield* findUser(userId);
    const profile = yield* getProfile(user.profileId);
    const settings = yield* getSettings(profile.settingsId);
    return Option.some(settings.displayName);
  });
}
```

### Array.find to Option

```typescript
// BEFORE
const item = items.find((i) => i.id === id);
if (item) {
  process(item);
}

// AFTER
Option.from(items.find((i) => i.id === id)).tap(process);
```

### Conditional Assignment to Option.filter

```typescript
// BEFORE
function getValidAge(input: number): number | null {
  return input >= 0 && input <= 150 ? input : null;
}

// AFTER
function getValidAge(input: number): Option<number> {
  return Option.fromPredicate(input, (age) => age >= 0 && age <= 150);
}
```

## Working with Async Code

### Async Option Composition

```typescript
// BEFORE
async function fetchUserProfile(userId: string): Promise<Profile | null> {
  const user = await fetchUser(userId);
  if (!user) return null;
  
  const profile = await fetchProfile(user.profileId);
  return profile;
}

// AFTER
async function fetchUserProfile(userId: string): Promise<Option<Profile>> {
  return Option.gen(async function* () {
    const user = yield* Option.await(fetchUser(userId));
    const profile = yield* Option.await(fetchProfile(user.profileId));
    return Option.some(profile);
  });
}
```

## Common Patterns Reference

See [references/option-patterns.md](references/option-patterns.md) for Option usage patterns.

## Workflow

1. **Check for source reference**: Look for `opensrc/` directory - if present, read the better-option source code for implementation details and patterns
2. **Audit**: Find null checks, optional chaining, and nullable returns in target module
3. **Wrap boundaries**: Use `Option.from()` at data access points
4. **Chain operations**: Convert if/else null checks to `.map`/`.andThen` or `Option.gen`
5. **Update signatures**: Change return types from `T | null` to `Option<T>`
6. **Update callers**: Propagate Option handling up call stack
7. **Test**: Verify both paths with `.match` or type narrowing

## Common Pitfalls

- **Over-wrapping**: Don't wrap every nullable. Start at boundaries, propagate inward.
- **Unwrap everywhere**: Avoid `.unwrap()` except at top-level handlers. Prefer `.match` or `.unwrapOr`.
- **Mixing paradigms**: Once a function returns Option, callers should handle it as Option (or explicitly `.unwrap`).
- **Ignoring Panic**: Callbacks that throw become Panic. Fix the bug, don't catch Panic.

## References

- [Option Patterns](references/option-patterns.md) - Common Option usage patterns
- `opensrc/` directory (if present) - Full better-option source code for deeper context
