> ## Documentation Index
> Fetch the complete documentation index at: https://brushysuite.gfrancodev.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Best Practices

# Best Practices

This guide presents the best practices for using `@brushy/di` efficiently and in an organized manner.

For package choice and framework setup (Express, Next.js, React Native), start with [Getting Started](/di/getting-started).

## Token Organization

### Use Symbols for Tokens

Prefer **`createToken("…")`** or **`Symbol("…")`**. Never use plain strings. Strings can collide when two modules use the same name; **`createToken` returns `Symbol(description)` at runtime**, so every token is unique.

```typescript theme={null}
// ✅ Good: createToken (Symbol + type inference)
const USER_SERVICE = createToken("USER_SERVICE");

// ✅ OK: raw Symbol
const USER_SERVICE = Symbol("USER_SERVICE");

// ❌ Avoid: string tokens
const USER_SERVICE = "USER_SERVICE";
```

### Use `createToken` for Type Inference

`createToken` is the recommended form of Symbol token: same collision safety, plus inference in `register`, `resolve`, `useInject`, factory `dependencies`, and `useInjectComponent` when registering with `useValue`:

```typescript theme={null}
import { createToken, deps } from "@brushy/di";

const LOGGER = createToken<Logger>("LOGGER");
const USER_SERVICE = createToken<UserService>("USER_SERVICE");

container.register(USER_SERVICE, {
  useFactory: (logger) => new UserService(logger),
  dependencies: deps([LOGGER]),
  lifecycle: "scoped",
});
```

### Centralize Token Definition

Keep all tokens in a centralized location:

```typescript theme={null}
// tokens.ts
export const TOKENS = {
  SERVICES: {
    USER_SERVICE: Symbol("USER_SERVICE"),
    AUTH_SERVICE: Symbol("AUTH_SERVICE"),
    PRODUCT_SERVICE: Symbol("PRODUCT_SERVICE"),
  },
  REPOSITORIES: {
    USER_REPOSITORY: Symbol("USER_REPOSITORY"),
    PRODUCT_REPOSITORY: Symbol("PRODUCT_REPOSITORY"),
  },
  UTILS: {
    LOGGER: Symbol("LOGGER"),
    CONFIG: Symbol("CONFIG"),
  },
  UI: {
    BUTTON: Symbol("BUTTON"),
    CARD: Symbol("CARD"),
  },
};
```

## Lifecycle

### Choose the Appropriate Lifecycle

* Use `singleton` for shared services (default)
* Use `transient` for instances that should not be shared
* Use `scoped` for instances that should be shared within a scope (e.g., HTTP request)
* Use `immutable` for state managers and instances that should never be invalidated

```typescript theme={null}
// Shared service
container.register(TOKENS.SERVICES.CONFIG_SERVICE, {
  useClass: ConfigService,
  lifecycle: "singleton", // or omit, as it's the default
});

// Unique instance per use
container.register(TOKENS.SERVICES.FILE_PROCESSOR, {
  useClass: FileProcessor,
  lifecycle: "transient",
});

// Instance per request
container.register(TOKENS.SERVICES.REQUEST_CONTEXT, {
  useClass: RequestContext,
  lifecycle: "scoped",
});

// State managers that should never be invalidated
container.register(TOKENS.SERVICES.QUERY_CLIENT, {
  useFactory: () => new QueryClient(),
  lifecycle: "immutable",
});
```

### Use Immutable Lifecycle for State Managers

When working with state management libraries like React Query, Redux, or Zustand, use the `immutable` lifecycle to ensure the instance is never invalidated:

```typescript theme={null}
// React Query
container.register(QUERY_CLIENT, {
  useFactory: () => new QueryClient(),
  lifecycle: "immutable"
});

// Redux Store
container.register(REDUX_STORE, {
  useFactory: () => createStore(rootReducer),
  lifecycle: "immutable"
});

// Zustand Store
container.register(APP_STORE, {
  useFactory: () => create(yourStore),
  lifecycle: "immutable"
});
```

### Clean Up Resources Properly

```typescript theme={null}
// Node.js - recommended: ALS middleware (cleans up on finish/close)
import { server } from "@brushy/di";
app.use(server.brushyRequestScope());

// Or wrap non-HTTP work
import { runInRequestScope } from "@brushy/di";
runInRequestScope(() => {
  const svc = container.resolve(REQUEST_CONTEXT);
});

// Manual cleanup when ALS is not available
app.use((req, res, next) => {
  next();
  res.on("finish", () => container.clearRequestScope());
});

// Use garbage collector to clean up unused instances
container.startGarbageCollector(60000, 30000);
```

## Application Structure

### Independent Modules

Organize your application into independent modules, each with its own container:

```typescript theme={null}
// users module
const userModule = new Container();
userModule.register(USER_SERVICE, { useClass: UserService });
userModule.register(USER_REPOSITORY, { useClass: UserRepository });

// products module
const productModule = new Container();
productModule.register(PRODUCT_SERVICE, { useClass: ProductService });
productModule.register(PRODUCT_REPOSITORY, { useClass: ProductRepository });

// main container
const appContainer = new Container();
appContainer.import(userModule, { prefix: "user" });
appContainer.import(productModule, { prefix: "product" });
```

### Injection in React Components

Prefer using hooks for injection in React components:

```tsx theme={null}
// ✅ Good: Use hooks
function UserList() {
  const userService = useInject(USER_SERVICE);
  // ...
}

// ❌ Avoid: Resolving directly in the component
function UserList() {
  const userService = container.resolve(USER_SERVICE);
  // ...
}
```

### Component injection (UI)

Register swappable UI on the **same container** as services. Prefer `new Container({ providers: [{ provide, useValue }] })` or `container.register(createToken("…"), { useValue: Component })`. Resolve in the shell with `useInjectComponent`:

```tsx theme={null}
// ✅ Good: container registration + hook (types inferred from useValue)
const container = new Container({ name: "app" });

const SIDEBAR = container.register(createToken("SIDEBAR"), {
  useValue: AcmeSidebar,
});

function AppShell() {
  const Sidebar = useInjectComponent(SIDEBAR);
  return <Sidebar onNavigate={navigate} />;
}

// ❌ Avoid: registerComponent helpers when you already use a container graph
registerComponent(SIDEBAR, AcmeSidebar);

// ❌ Avoid: string tokens (use createToken / Symbol instead)
const SIDEBAR = "SIDEBAR";

// ❌ Avoid: redundant explicit generics when useValue carries the type
const SIDEBAR = createToken<React.ComponentType<SidebarProps>>("SIDEBAR");
```

Explicit `createToken<T>()` is optional. Use it only when the token contract must exist before the implementation (shared `tokens.ts`). See [Component Injection](/di/component-injection).

## Performance

### Promise Caching

Use promise caching to avoid multiple API calls:

```typescript theme={null}
// ✅ Good: Use promise caching
function UserProfile({ userId }) {
  const userService = useInject(USER_SERVICE);
  const user = use(userService.getUserById(userId));
  return <div>{user.name}</div>;
}

// ❌ Avoid: Creating new promises on each render
function UserProfile({ userId }) {
  const userService = useInject(USER_SERVICE);
  const [user, setUser] = useState(null);

  useEffect(() => {
    userService.getUserById(userId).then(setUser);
  }, [userId, userService]);

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}
```

### Lazy Loading

Use `useInjectLazy` to load heavy dependencies only when needed:

```tsx theme={null}
function ReportPage() {
  const reportService = useInjectLazy(REPORT_SERVICE);

  const generateReport = () => {
    reportService.generate();
  };

  return (
    <div>
      <button onClick={generateReport}>Generate Report</button>
    </div>
  );
}
```

## Testability

### Test Containers

Create specific containers for tests:

```typescript theme={null}
// test container
const testContainer = new Container();
testContainer.register(USER_SERVICE, {
  useClass: MockUserService
});

// test component
function renderWithDI(ui) {
  return render(
    <BrushyDIProvider container={testContainer}>
      {ui}
    </BrushyDIProvider>
  );
}

// test
test('renders user list', () => {
  renderWithDI(<UserList />);
  // ...
});
```

### Easy Mocking

Use `useValue` to inject mocks in tests:

```typescript theme={null}
// service mock
const mockUserService = {
  getUsers: jest.fn().mockResolvedValue([
    { id: 1, name: "User 1" },
    { id: 2, name: "User 2" },
  ]),
};

// register mock
testContainer.register(USER_SERVICE, {
  useValue: mockUserService,
});
```

## Observability

### Monitor the Container

Use the monitor to debug issues:

```typescript theme={null}
// create monitor
const containerMonitor = monitor.create(container, {
  eventTypes: ["resolve", "error"],
  logToConsole: true,
});

// analyze events after operations
const events = containerMonitor.getEvents();
const stats = containerMonitor.getStats();

console.log(`Error rate: ${stats.errorRate * 100}%`);
console.log(`Success rate: ${stats.resolveSuccessRate * 100}%`);
```

### Verify Immutable Integrity

Use the `verifyImmutableIntegrity` method to ensure immutable instances maintain their identity:

```typescript theme={null}
// Create a verifier function
const verifyIntegrity = container.verifyImmutableIntegrity();

// Check integrity at critical points in your application
function checkSystemIntegrity() {
  const queryClientIntact = verifyIntegrity(QUERY_CLIENT);
  const reduxStoreIntact = verifyIntegrity(REDUX_STORE);
  
  if (!queryClientIntact || !reduxStoreIntact) {
    console.error("Immutable integrity violation detected!");
    // Take appropriate action
  }
}
```

### Detailed Logging

Enable debug mode for detailed logging:

```typescript theme={null}
const container = new Container({
  debug: true,
  name: "AppContainer",
});
```

## Security

### Validation of Dependencies

Validate dependencies when registering them:

```typescript theme={null}
function registerService(token, serviceClass, dependencies = []) {
  // Check if all dependencies are registered
  for (const dep of dependencies) {
    if (!container.registry.has(dep)) {
      throw new Error(`Dependency not registered: ${String(dep)}`);
    }
  }

  container.register(token, {
    useClass: serviceClass,
    dependencies,
  });
}
```

### Avoid Exposing Sensitive Services

Don't expose sensitive services directly:

```typescript theme={null}
// ✅ Good: Expose only necessary methods
container.register(AUTH_SERVICE, {
  useFactory: () => {
    const authService = new AuthService();

    // Return only public methods
    return {
      login: authService.login.bind(authService),
      logout: authService.logout.bind(authService),
      isAuthenticated: authService.isAuthenticated.bind(authService),
    };
  },
});

// ❌ Avoid: Exposing the entire service
container.register(AUTH_SERVICE, {
  useClass: AuthService,
});
```

## Complete Architecture Example

```
src/
├── di/
│   ├── tokens.ts           # All token definitions
│   ├── container.ts        # Main container configuration
│   └── modules/            # DI modules
│       ├── auth.module.ts
│       ├── user.module.ts
│       └── product.module.ts
├── services/               # Service implementations
│   ├── auth/
│   ├── user/
│   └── product/
├── components/             # React components
│   ├── providers/          # DI providers
│   │   └── AppProvider.tsx # Main provider
│   └── ...
└── hooks/                  # Custom hooks
    └── useAuth.ts          # Hook using useInject
```

### tokens.ts

```typescript theme={null}
export const TOKENS = {
  SERVICES: {
    AUTH: Symbol("AUTH_SERVICE"),
    USER: Symbol("USER_SERVICE"),
    PRODUCT: Symbol("PRODUCT_SERVICE"),
  },
  REPOSITORIES: {
    USER: Symbol("USER_REPOSITORY"),
    PRODUCT: Symbol("PRODUCT_REPOSITORY"),
  },
  UI: {
    BUTTON: Symbol("BUTTON"),
    CARD: Symbol("CARD"),
  },
  STATE: {
    QUERY_CLIENT: Symbol("QUERY_CLIENT"),
    STORE: Symbol("STORE"),
  }
};
```

### container.ts

```typescript theme={null}
import { Container } from "@brushy/di";
import { authModule } from "./modules/auth.module";
import { userModule } from "./modules/user.module";
import { productModule } from "./modules/product.module";
import { TOKENS } from "./tokens";
import { QueryClient } from "react-query";

export function createAppContainer() {
  const container = new Container({ name: "AppContainer" });

  // Register state managers with immutable lifecycle
  container.register(TOKENS.STATE.QUERY_CLIENT, {
    useFactory: () => new QueryClient(),
    lifecycle: "immutable"
  });

  // Import modules
  container.import(authModule);
  container.import(userModule);
  container.import(productModule);

  // Start garbage collector
  container.startGarbageCollector();

  return container;
}

export const appContainer = createAppContainer();
```

### AppProvider.tsx

```tsx theme={null}
import { BrushyDIProvider, inject } from "@brushy/di";
import { appContainer } from "../di/container";
import { QueryClientProvider } from "react-query";
import { TOKENS } from "../di/tokens";

// Set global container
inject.setGlobalContainer(appContainer);

export function AppProvider({ children }) {
  // Get the immutable query client
  const queryClient = inject.resolve(TOKENS.STATE.QUERY_CLIENT);

  return (
    <BrushyDIProvider container={appContainer}>
      <QueryClientProvider client={queryClient}>
        {children}
      </QueryClientProvider>
    </BrushyDIProvider>
  );
}
```

### useAuth.ts

```typescript theme={null}
import { useInject } from "@brushy/di";
import { TOKENS } from "../di/tokens";
import { useState, useEffect } from "react";

export function useAuth() {
  const authService = useInject(TOKENS.SERVICES.AUTH);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    setIsAuthenticated(authService.isAuthenticated());

    // Subscribe to authentication changes
    const unsubscribe = authService.subscribe((state) => {
      setIsAuthenticated(state.isAuthenticated);
    });

    return unsubscribe;
  }, [authService]);

  return {
    isAuthenticated,
    login: authService.login,
    logout: authService.logout,
  };
}
```
