# Effect Library Development Patterns

## OVERVIEW

Fundamental patterns for developing high-quality, type-safe code within the Effect library ecosystem. These patterns ensure consistency, reliability, and maintainability across the codebase.

## CRITICAL FORBIDDEN PATTERNS

### NEVER: try-catch in Effect.gen

**REASON**: Effect generators handle errors through the Effect type system, not JavaScript exceptions.

```typescript
// WRONG - This will cause runtime errors
Effect.gen(function* () {
  try {
    const result = yield* someEffect;
    return result;
  } catch (error) {
    // This will never be reached and breaks Effect semantics
    console.error(error);
  }
});

// CORRECT - Use Effect's built-in error handling
Effect.gen(function* () {
  const exit = yield* Effect.exit(someEffect);
  if (exit._tag === "Failure") {
    // Handle error case properly
    console.error("Effect failed:", exit.cause);
    return yield* Effect.fail("Handled error");
  }
  return exit.value;
});
```

### NEVER: Type Assertions

**REASON**: Type assertions hide real type errors and break TypeScript's safety guarantees.

```typescript
// FORBIDDEN - These break type safety
const value = something as any;
const value = something as never;
const value = something as unknown;

// CORRECT - Fix the underlying type issues
// Use proper generic type parameters
function processValue<T>(value: T): Effect.Effect<T, never, never> {
  return Effect.succeed(value);
}

// Use proper Effect constructors
const safeValue = Effect.try(() => JSON.parse(jsonString));
```

## MANDATORY PATTERNS

### return yield\* Pattern for Errors

**CRITICAL**: Always use `return yield*` when yielding terminal effects.

```typescript
// CORRECT - Makes termination explicit
Effect.gen(function* () {
  if (invalidCondition) {
    return yield* Effect.fail("Validation failed");
  }

  if (shouldInterrupt) {
    return yield* Effect.interrupt;
  }

  // Continue with normal flow
  const result = yield* someOtherEffect;
  return result;
});

// WRONG - Missing return keyword leads to unreachable code
Effect.gen(function* () {
  if (invalidCondition) {
    yield* Effect.fail("Validation failed"); // Missing return!
    // Unreachable code after error!
  }
});
```

## CORE DEVELOPMENT PATTERNS

### Effect.gen Composition Pattern

Use `Effect.gen` for sequential operations with proper error propagation:

```typescript
import { Console, Effect } from "effect";

const processData = (input: string) =>
  Effect.gen(function* () {
    // Validate input
    if (input.length === 0) {
      return yield* Effect.fail("Input cannot be empty");
    }

    // Transform data
    const processed = yield* Effect.try({
      try: () => JSON.parse(input),
      catch: error => `Invalid JSON: ${error}`,
    });

    // Log progress
    yield* Console.log(`Processed: ${JSON.stringify(processed)}`);

    return processed;
  });
```

### Effect.gen vs Effect.fn vs Effect.fnUntraced

Choose the right function constructor based on your use case:

| Feature         | `Effect.gen`               | `Effect.fn`                    | `Effect.fnUntraced`            |
| --------------- | -------------------------- | ------------------------------ | ------------------------------ |
| **Purpose**     | One-off effect composition | Reusable effectful functions   | Internal/performance-critical  |
| **Returns**     | `Effect<A, E, R>`          | `(...args) => Effect<A, E, R>` | `(...args) => Effect<A, E, R>` |
| **Tracing**     | Uses parent span           | Creates new span + stack trace | No tracing overhead            |
| **Performance** | Standard                   | Overhead for tracing           | Minimal overhead               |
| **Use case**    | Inline composition         | Public API methods             | Library internals              |

#### Effect.gen - Inline Composition

Use for one-off effect composition that doesn't need to be reused as a function:

```typescript
import { Effect } from "effect";

// One-off effect, no function wrapper needed
const program = Effect.gen(function* () {
  const user = yield* fetchUser(id);
  const posts = yield* fetchPosts(user.id);
  return { user, posts };
});
```

#### Effect.fn - Traced Reusable Functions (Public API)

Use for reusable effectful functions that benefit from tracing and stack traces:

```typescript
import { Effect } from "effect";

// Creates a traced function with span + stack capture
const fetchUserPosts = Effect.fn("fetchUserPosts")(function* (userId: string) {
  yield* Effect.annotateCurrentSpan("userId", userId);
  const user = yield* fetchUser(userId);
  const posts = yield* fetchPosts(user.id);
  return { user, posts };
});

// Calling creates a span named "fetchUserPosts" with stack traces
await Effect.runPromise(fetchUserPosts("123"));
```

`Effect.fn` also supports piping transformations after the function body:

```typescript
const fetchWithTimeout = Effect.fn("fetchWithTimeout")(
  function* (url: string) {
    return yield* Effect.tryPromise(() => fetch(url));
  },
  Effect.timeout("5 seconds"),
  Effect.retry({ times: 3 })
);
```

#### Effect.fnUntraced - Untraced Functions (Library Internals)

Use for internal implementations where tracing overhead is unacceptable:

```typescript
import { Effect, Scope } from "effect";

// No tracing overhead - used in Stream, Channel, Sink internals
const internalTransform = Effect.fnUntraced(function* (pull, scope) {
  const reader = options.evaluate().getReader();
  yield* Scope.addFinalizer(
    scope,
    Effect.sync(() => reader.releaseLock())
  );
  // ... internal implementation
});
```

#### When to Use What

**Use `Effect.gen`** when:

- Writing inline effect composition
- One-off operations that don't need to be reused
- Inside other functions already being traced

**Use `Effect.fn`** when:

- Creating reusable effectful functions
- Building public API methods
- You want automatic tracing/spans for debugging
- Error stack traces matter for users

**Use `Effect.fnUntraced`** when:

- Building internal library implementations
- Performance is critical (hot paths)
- Function is called many times per operation
- Tracing overhead is unacceptable

### Error Handling with Data.TaggedError

Create structured, typed errors using `Data.TaggedError`:

```typescript
import { Data, Effect } from "effect";

// Define custom error types
class ValidationError extends Data.TaggedError("ValidationError")<{
  field: string;
  message: string;
}> {}

class NetworkError extends Data.TaggedError("NetworkError")<{
  status: number;
  url: string;
}> {}

// Use in operations
const validateAndFetch = (url: string) =>
  Effect.gen(function* () {
    if (!url.startsWith("https://")) {
      return yield* Effect.fail(
        new ValidationError({
          field: "url",
          message: "URL must use HTTPS",
        })
      );
    }

    const response = yield* Effect.tryPromise({
      try: () => fetch(url),
      catch: () => new NetworkError({ status: 0, url }),
    });

    if (!response.ok) {
      return yield* Effect.fail(
        new NetworkError({
          status: response.status,
          url,
        })
      );
    }

    return response;
  });
```

### Resource Management Pattern

Use `Effect.acquireUseRelease` for automatic resource cleanup:

```typescript
import { Console, Effect } from "effect";

// Resource acquisition pattern
const withDatabase = <A, E>(
  operation: (db: Database) => Effect.Effect<A, E, never>
): Effect.Effect<A, E | DatabaseError, never> =>
  Effect.acquireUseRelease(
    // Acquire
    Effect.tryPromise({
      try: () => createDatabaseConnection(),
      catch: error => new DatabaseError({ cause: error }),
    }),
    // Use
    operation,
    // Release
    db => Effect.promise(() => db.close())
  );
```

### Layer Composition Pattern

Build applications using layered architecture:

```typescript
import { Context, Effect, Layer } from "effect";

// Define service interfaces
class DatabaseService extends Context.Tag("DatabaseService")<
  DatabaseService,
  {
    readonly query: (sql: string) => Effect.Effect<unknown[], DatabaseError, never>;
  }
>() {}

class UserService extends Context.Tag("UserService")<
  UserService,
  {
    readonly getUser: (id: string) => Effect.Effect<User, UserError, never>;
  }
>() {}

// Implement services as layers
const DatabaseServiceLive = Layer.succeed(
  DatabaseService,
  DatabaseService.of({
    query: sql =>
      Effect.tryPromise({
        try: () => database.execute(sql),
        catch: error => new DatabaseError({ cause: error }),
      }),
  })
);

const UserServiceLive = Layer.effect(
  UserService,
  Effect.gen(function* () {
    const db = yield* DatabaseService;

    return UserService.of({
      getUser: id =>
        Effect.gen(function* () {
          const rows = yield* db.query(`SELECT * FROM users WHERE id = '${id}'`);
          if (rows.length === 0) {
            return yield* Effect.fail(new UserError({ message: "User not found" }));
          }
          return rows[0] as User;
        }),
    });
  })
);

// Compose layers
const AppLayer = UserServiceLive.pipe(Layer.provide(DatabaseServiceLive));
```

## DEVELOPMENT WORKFLOW PATTERNS

### Validation Checkpoint Pattern

Run comprehensive validation after implementation:

```bash
# 1. Lint all modified files
pnpm lint --fix

# 2. Check types
pnpm check

# 3. Run tests
pnpm test

# 4. Build project
pnpm build
```

### Progressive Implementation Pattern

Break complex features into validated increments:

```typescript
// Step 1: Basic structure with types
interface FeatureConfig {
  readonly option1: string;
  readonly option2: number;
}

// Step 2: Core implementation
const createFeature = (config: FeatureConfig) =>
  Effect.gen(function* () {
    yield* Console.log("Feature created");
    return { config };
  });

// Step 3: Add error handling
const createFeatureWithValidation = (config: FeatureConfig) =>
  Effect.gen(function* () {
    if (config.option2 < 0) {
      return yield* Effect.fail("Option2 must be positive");
    }

    const feature = yield* createFeature(config);
    return feature;
  });

// Step 4: Add comprehensive functionality
// ... continue building incrementally
```

## MODULE EXPORT PATTERN

Structure module exports for clarity and discoverability:

````typescript
// ModuleName.ts

/**
 * Creates a new instance with the given configuration.
 *
 * @example
 * ```ts
 * import { ModuleName } from "effect"
 *
 * const instance = ModuleName.create({ value: 42 })
 * ```
 *
 * @since 1.0.0
 * @category constructors
 */
export const create: <A>(config: Config<A>) => Effect.Effect<Instance<A>, never, never> = config =>
  Effect.succeed({ config });

/**
 * Transforms an instance using the provided function.
 *
 * @example
 * ```ts
 * import { ModuleName, Effect } from "effect"
 *
 * const program = Effect.gen(function*() {
 *   const instance = yield* ModuleName.create({ value: 42 })
 *   const transformed = yield* ModuleName.map(instance, x => x * 2)
 *   return transformed
 * })
 * ```
 *
 * @since 1.0.0
 * @category combinators
 */
export const map: <A, B>(
  instance: Instance<A>,
  f: (a: A) => B
) => Effect.Effect<Instance<B>, never, never> = (instance, f) =>
  Effect.succeed({ config: f(instance.config) });
````
