# How to Configure Dependency Injection Contracts in Common Types for Freebuff

> Learn how to configure dependency injection contracts in common types using TypeScript interfaces in Freebuff. Streamline your code with clear contracts and efficient implementations.

- Repository: [Codebuff/freebuff](https://github.com/CodebuffAI/freebuff)
- Tags: how-to-guide
- Published: 2026-08-20

---

**Use TypeScript interfaces in `common/src/types/contracts/` to define DI contracts, then wire production implementations in [`common/src/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env.ts) and test mocks via [`common/src/env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-process.ts).**

The **Freebuff** repository (CodebuffAI/freebuff) implements a lightweight, explicit dependency injection system using TypeScript contracts. These contracts live in `common/src/types/contracts/` and strictly type all runtime-configurable values—from environment variables to service abstractions. This guide walks through how to define, implement, and inject these contracts correctly.

## Understanding the DI Contract Architecture

Freebuff's DI system separates **interface definition** from **implementation**. This enables compile-time safety and seamless test mocking without external DI frameworks.

| Contract | Purpose | Location |
|----------|---------|----------|
| **EnvContract** | Environment variable schema | [`common/src/types/contracts/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/env.ts) |
| **AnalyticsContract** | Analytics service interface | [`common/src/types/contracts/analytics.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/analytics.ts) |
| **SchedulerContract** | Timer/scheduler hook for CLI | [`common/src/types/contracts/scheduler.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/scheduler.ts) |

The pattern follows three layers:

1. **Contracts** – Pure TypeScript interfaces with zero runtime dependencies
2. **Production implementations** – Concrete objects reading `process.env` or external services
3. **Test factories** – Helper functions generating mock implementations

## Defining a New DI Contract

Create or extend an interface in `common/src/types/contracts/` following the existing `EnvContract` pattern.

```ts
// common/src/types/contracts/env.ts
export interface EnvContract {
  /** OpenAI API key – required for any LLM call */
  OPENAI_API_KEY: string;
  /** Bun runtime version – used for feature gating */
  BUN_VERSION: string;
  /** Optional flag that disables telemetry */
  DISABLE_TELEMETRY?: boolean;
  /** New experimental feature flag */
  ENABLE_EXPERIMENT?: boolean;
}

```

Key conventions observed in Freebuff contracts:

- **JSDoc comments** describe purpose and constraints
- **Required fields** have no `?` modifier
- **Optional flags** use `?` with sensible defaults in implementations

## Implementing the Contract in Production

Production code lives adjacent to the contracts, not inside them.

```ts
// common/src/env.ts
import type { EnvContract } from './types/contracts/env';

export const env: EnvContract = {
  OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? '',
  BUN_VERSION: process.env.BUN_VERSION ?? '',
  DISABLE_TELEMETRY: process.env.DISABLE_TELEMETRY === 'true',
  ENABLE_EXPERIMENT: process.env.ENABLE_EXPERIMENT === 'true',
};

```

This module provides the **default implementation** imported throughout the codebase. It coerces environment strings to proper types and supplies empty-string fallbacks for required fields (validation happens at consumption time).

## Creating Injectable Test Mocks

The [`env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/env-process.ts) module exports a factory for building partial or complete contract implementations.

```ts
// common/src/env-process.ts
import type { EnvContract } from './types/contracts/env';

export const makeEnv = (overrides: Partial<EnvContract> = {}): EnvContract => ({
  OPENAI_API_KEY: '',
  BUN_VERSION: '',
  DISABLE_TELEMETRY: false,
  ENABLE_EXPERIMENT: false,
  ...overrides,
});

```

The `makeEnv` function spreads defaults first, then overrides, ensuring all required fields are always present. Tests pass only the values they need to vary.

## Injecting Contracts into Application Code

Modules receive their dependencies through constructor injection or function parameters, never importing `process.env` directly.

```ts
// src/agents/base2/base2.ts
import type { EnvContract } from '../../common/src/types/contracts/env';
import { makeEnv } from '../../common/src/env-process';

export class Base2 {
  constructor(private readonly env: EnvContract = makeEnv()) {}

  canRunExperiment(): boolean {
    return !!this.env.ENABLE_EXPERIMENT;
  }

  async callOpenAI(prompt: string) {
    const apiKey = this.env.OPENAI_API_KEY;
    if (!apiKey) throw new Error('OPENAI_API_KEY not configured');
    // ... use apiKey ...
  }
}

```

Providing `makeEnv()` as a default argument ensures the class works in production (when called with no arguments) while remaining fully testable.

## Configuring CI-Specific Implementations

For continuous integration environments, Freebuff provides [`env-ci.ts`](https://github.com/CodebuffAI/freebuff/blob/main/env-ci.ts) with specialized overrides.

```ts
// common/src/env-ci.ts
import type { EnvContract } from './types/contracts/env';

export const env: EnvContract = {
  OPENAI_API_KEY: process.env.CI_OPENAI_API_KEY ?? 'ci-mock-key',
  BUN_VERSION: process.env.BUN_VERSION ?? '1.0.0-ci',
  DISABLE_TELEMETRY: true, // Always disable in CI
  ENABLE_EXPERIMENT: true, // Enable experiments in CI builds
};

```

CI pipelines import from [`env-ci.ts`](https://github.com/CodebuffAI/freebuff/blob/main/env-ci.ts) instead of [`env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/env.ts) to inject CI-appropriate behavior without code changes.

## Complete Configuration Example: Adding a Feature Flag

Step-by-step workflow for extending the DI system:

**Step 1: Extend the contract**

```ts
// common/src/types/contracts/env.ts
export interface EnvContract {
  // existing fields...
  MAX_BATCH_SIZE?: number; // New configurable limit
}

```

**Step 2: Update production implementation**

```ts
// common/src/env.ts
export const env: EnvContract = {
  // existing fields...
  MAX_BATCH_SIZE: parseInt(process.env.MAX_BATCH_SIZE ?? '10', 10),
};

```

**Step 3: Add test default**

```ts
// common/src/env-process.ts
export const makeEnv = (overrides: Partial<EnvContract> = {}): EnvContract => ({
  // existing defaults...
  MAX_BATCH_SIZE: 10,
  ...overrides,
});

```

**Step 4: Consume in application code**

```ts
// src/batch-processor.ts
import type { EnvContract } from '../common/src/types/contracts/env';

export function createBatchProcessor(env: EnvContract) {
  const limit = env.MAX_BATCH_SIZE ?? 10;
  return {
    process: (items: unknown[]) => items.slice(0, limit),
  };
}

```

**Step 5: Test with mock**

```ts
import { createBatchProcessor } from './batch-processor';
import { makeEnv } from '../common/src/env-process';

test('respects batch size limit', () => {
  const env = makeEnv({ MAX_BATCH_SIZE: 2 });
  const processor = createBatchProcessor(env);
  
  const result = processor.process([1, 2, 3, 4, 5]);
  expect(result).toHaveLength(2);
});

```

## Key Files Reference

| File | Role | Implementation Pattern |
|------|------|------------------------|
| [`common/src/types/contracts/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/env.ts) | Environment contract definition | Pure interface |
| [`common/src/types/contracts/analytics.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/analytics.ts) | Analytics service contract | Interface with method signatures |
| [`common/src/types/contracts/scheduler.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/scheduler.ts) | Scheduler abstraction | Interface with callback types |
| [`common/src/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env.ts) | Production environment implementation | Reads `process.env` |
| [`common/src/env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-process.ts) | Test factory for `EnvContract` | `makeEnv()` with spread defaults |
| [`common/src/env-ci.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-ci.ts) | CI-specific environment override | Hardcoded CI values |

## Summary

- **Define contracts** as TypeScript interfaces in `common/src/types/contracts/` with clear JSDoc documentation
- **Implement production values** in [`common/src/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env.ts), coercing environment strings to proper types
- **Generate test mocks** using `makeEnv` from [`common/src/env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-process.ts) to inject partial overrides
- **Inject dependencies** via constructor parameters or function arguments, never accessing `process.env` directly in business logic
- **Support multiple environments** by creating alternative implementations (e.g., [`env-ci.ts`](https://github.com/CodebuffAI/freebuff/blob/main/env-ci.ts)) that adhere to the same contract interface

## Frequently Asked Questions

### How do I add a required environment variable to Freebuff?

Extend `EnvContract` in [`common/src/types/contracts/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/types/contracts/env.ts) with a non-optional property, then update [`common/src/env.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env.ts) to read it from `process.env`. Add a default value in [`common/src/env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-process.ts)'s `makeEnv` function to prevent test breakage.

### Can I use Freebuff's DI contracts for non-environment dependencies?

Yes. Create a new interface in `common/src/types/contracts/` (following [`analytics.ts`](https://github.com/CodebuffAI/freebuff/blob/main/analytics.ts) or [`scheduler.ts`](https://github.com/CodebuffAI/freebuff/blob/main/scheduler.ts) as models), implement it in a service module, and inject via constructor parameters. The pattern works for any replaceable dependency.

### Why does Freebuff use explicit contracts instead of a DI framework like InversifyJS?

According to the Freebuff source code, the explicit contract pattern provides **compile-time type safety without runtime overhead** and **zero external dependencies** for the common types package. The small surface area of injected values makes a full DI framework unnecessary.

### How do I override a single value in a unit test without repeating all defaults?

Import `makeEnv` from [`common/src/env-process.ts`](https://github.com/CodebuffAI/freebuff/blob/main/common/src/env-process.ts) and pass only the fields you want to override: `makeEnv({ OPENAI_API_KEY: 'test-key' })`. The spread operator merges your overrides with sensible defaults for all other fields.