npm package: @kazejs/ioc
Description: Biblioteca de Inversão de Controle (IoC) para TypeScript e JavaScript, para Deno e Node.js.
Latest version: 0.0.5

# Injeção de Dependências (IoC)

Biblioteca standalone, sem dependências externas, para Injeção de Dependências
(IoC) em aplicações Deno/NodeJS/TypeScript, com suporte a namespaces, escopos e
lifecycle hooks.

Inclui middleware para integração com frameworks web como Hono.

## Características

- ✅ **Zero dependências** - Funciona standalone
- ✅ **Multi-plataforma** - Deno, Node.js e navegadores
- ✅ **TypeScript nativo** - Totalmente tipado
- ✅ **Namespaces isolados** - Múltiplos containers independentes
- ✅ **Gestão de escopos** - Singleton, Scoped, Transient
- ✅ **Lifecycle hooks** - onApplicationRegister/Bootstrap/Shutdown
- ✅ **Middleware web** - Integração com Hono (extensível para outros
  frameworks)
- ✅ **Injeção automática** - Resolve dependências recursivamente
- ✅ **Factories dinâmicas** - Criação sob demanda com contexto

## Instalação

### Deno

```typescript
import { IoC, LifeTime } from "@kazejs/ioc";
import { contextIoC } from "@kazejs/ioc/hono"; // Para Hono
```

### Node.js / NPM

```bash
npm install @kazejs/ioc
```

```typescript
import { IoC, LifeTime } from "@kazejs/ioc";
import { contextIoC } from "@kazejs/ioc/hono"; // Para Hono (requer: npm install hono)
```

## Visão Geral

Container de inversão de controle para gerenciamento de dependências:

- Registro de serviços com diferentes ciclos de vida
- Suporte a namespaces isolados
- Injeção automática em contexto de requisições
- Gerenciamento de escopos (singleton, scoped, transient)
- Lifecycle hooks para inicialização e finalização

## Tipos de LifeTime

```typescript
enum LifeTime {
  SINGLETON = "singleton", // Uma instância para toda a aplicação
  SCOPED = "scope", // Uma instância por escopo (por requisição)
  TRANSIENT = "transient", // Nova instância a cada solicitação
}
```

## API Principal

### IoC (Classe estática)

Ponto de acesso global para gerenciamento de containers:

```typescript
import { IoC, LifeTime } from "@kazejs/ioc";

// Registrar provider
IoC.register({
  token: "myService",
  useValue: new MyService(),
  lifeTime: LifeTime.SINGLETON,
});

// Usar serviço
const service = IoC.use<MyService>("myService");

// Trabalhar com namespaces
IoC.register({ token: "db", useValue: dbService }, "database");
const db = IoC.use("db", "database");
```

### Container

Implementação do container de DI:

```typescript
import { Container, LifeTime } from "@kazejs/ioc";

const container = new Container("myApp");

// Registrar por valor
container.registerValue("config", { port: 3000 }, LifeTime.SINGLETON);

// Registrar por factory
container.registerFactory(
  "logger",
  (c) => new Logger(c.get("config")),
  LifeTime.SINGLETON,
);

// Obter serviços
const config = container.get("config");
const logger = container.get("logger");

// Trabalhar com escopos
const scopeId = container.createScope();
const scopedService = container.getByScope("scopedService", scopeId);
container.clearScope(scopeId);
```

## Lifecycle Hooks

Serviços podem implementar hooks de inicialização e finalização:

```typescript
import {
  OnApplicationBootstrap,
  OnApplicationRegister,
  OnApplicationShutdown,
} from "@kazejs/ioc";

class DatabaseService
  implements
    OnApplicationRegister,
    OnApplicationBootstrap,
    OnApplicationShutdown {
  private connection: any;

  // Hook de registro
  async onApplicationRegister(): Promise<void> {
    console.log("Registrando provedor de banco de dados...");
    this.client = this.registerDbClient();
  }

  // Hook de inicialização
  async onApplicationBootstrap(): Promise<void> {
    console.log("Inicializando conexão com banco de dados...");
    this.connection = await this.client.connect();
  }

  // Hook de finalização
  async onApplicationShutdown(): Promise<void> {
    console.log("Fechando conexão com banco de dados...");
    await this.connection.close();
  }

  private registerDbClient() {
    return {
      connect: () => {
        return {
          close: async () => {},
        };
      },
    };
  }

  query(sql: string) {
    return this.connection.query(sql);
  }
}

// Registro
IoC.register({
  token: "database",
  useClass: DatabaseService,
  lifeTime: LifeTime.SINGLETON,
});

// Os hooks são chamados automaticamente
const container = IoC.create("default");
await container.bootstrapServices(); // Chama onApplicationBootstrap
await container.shutdownServices(); // Chama onApplicationShutdown
```

## Middleware para Hono

Injeção automática de IoC no contexto de requisições:

```typescript
import { Hono } from "hono";
import { IoC } from "@kazejs/ioc";
import { contextIoC } from "@kazejs/ioc/hono";
import type { IocVariables } from "@kazejs/ioc/types";

type Env = {
  Variables: IocVariables & {
    // Suas variáveis customizadas aqui
    requestId: string;
  };
};

const app = new Hono<Env>();
const container = IoC.ns("default");

app.use(contextIoC(container));

app.get("/", (c) => {
  // Acessar container
  const ioc = c.var.ioc; // ou c.get("ioc");

  // Usar função helper (com escopo automático)
  const service = c.get("use")<MyService>("myService");

  // Ou usar o método ctx.use() diretamente (requer declaração de tipo)
  const service2 = c.use<MyService>("myService");

  return c.json({ message: "OK" });
});
```

### Usando `ctx.use()` com TypeScript

O middleware adiciona dinamicamente o método `ctx.use()` ao contexto do Hono.
Para ter suporte completo do TypeScript a este método, adicione a seguinte
declaração de tipo no seu projeto (por exemplo, em `types.ts` ou no início do
arquivo):

```typescript
import type { ProviderToken } from "@kazejs/ioc/types";

declare module "hono" {
  interface Context {
    // deno-lint-ignore no-explicit-any
    use: <T>(provider: ProviderToken | (new (...args: any) => T)) => T;
  }
}
```

**Nota:** Esta declaração de tipo **não é compatível com JSR/Deno** ao publicar
pacotes, pois módulos ambientes não são suportados. Se você está desenvolvendo
um pacote para publicação no JSR, prefira usar `c.get("use")` ao invés de
`c.use()`.

#### Duas formas de usar:

```typescript
app.get("/", (c) => {
  // Opção 1: Usando c.get("use") - sempre funciona, tipagem garantida
  const service1 = c.get("use")<MyService>("myService");

  // Opção 2: Usando c.use() - requer declaração de tipo acima
  const service2 = c.use<MyService>("myService");

  // Ambas são equivalentes e trabalham com escopo automático
});
```

## Padrões de Uso

### 1. Registro de Serviços

```typescript
// Por valor
IoC.register({
  token: "config",
  useValue: { port: 3000, host: "localhost" },
});

// Por classe
IoC.register({
  token: "logger",
  useClass: ConsoleLogger,
  lifeTime: LifeTime.SINGLETON,
});

// Por factory
IoC.register({
  token: "database",
  useFactory: (c) => new Database(c.use("config")),
  lifeTime: LifeTime.SINGLETON,
});

// Factory inline com useFactory()
const container = IoC.ns("default");
const useDemo = container.useFactory((c) => {
  return new DemoService(c.get("config"));
});

const service = useDemo();
```

### 2. Tokens de Serviço

```typescript
// String
IoC.register({ token: "logger", useClass: Logger });
const logger = IoC.use<Logger>("logger");

// Symbol
const DB_TOKEN = Symbol("database");
IoC.register({ token: DB_TOKEN, useClass: Database });
const db = IoC.use<Database>(DB_TOKEN);

// Construtor
IoC.register({ token: Logger, useClass: Logger });
const logger = IoC.use(Logger);
```

### 3. Serviços com Escopo

```typescript
// Registro
IoC.register({
  token: "requestService",
  useFactory: () => new RequestService(),
  lifeTime: LifeTime.SCOPED,
});

// Uso em middleware Hono (escopo automático)
app.get("/", (c) => {
  const service = c.get("use")<RequestService>("requestService");
  // Mesmo serviço durante toda a requisição
});

// Uso manual com escopo
const container = IoC.ns("default");
const scopeId = container.createScope();
const service1 = container.getByScope("requestService", scopeId);
const service2 = container.getByScope("requestService", scopeId);
// service1 === service2
container.clearScope(scopeId);
```

### 4. Inicialização e Shutdown

```typescript
const container = IoC.ns("default");

// Inicializar todos os serviços
await container.bootstrapServices();

// Finalizar todos os serviços
await container.shutdownServices();
```

## Namespaces

Isolamento de containers por contexto:

```typescript
// Container padrão
IoC.register({ token: "service", useClass: Service });

// Container específico
IoC.register({ token: "service", useClass: TestService }, "test");

// Uso
const prodService = IoC.use("service"); // default namespace
const testService = IoC.use("service", "test"); // test namespace

// Acesso direto a namespace
const testContainer = IoC.ns("test");
testContainer.register({ token: "custom", useValue: {} });
```

## Exemplo Completo

```typescript
import { Hono } from "hono";
import {
  IoC,
  LifeTime,
  OnApplicationBootstrap,
  OnApplicationShutdown,
} from "@kazejs/ioc";
import { contextIoC } from "@kazejs/ioc/hono";

// Serviços com Lifecycle
class Logger implements OnApplicationBootstrap {
  async onApplicationBootstrap() {
    console.log("Logger inicializado");
  }

  log(message: string) {
    console.log(message);
  }
}

class DatabaseService implements OnApplicationBootstrap, OnApplicationShutdown {
  private connection: any;

  async onApplicationBootstrap() {
    console.log("Conectando ao banco de dados...");
    this.connection = { query: (sql: string) => [] };
  }

  async onApplicationShutdown() {
    console.log("Desconectando do banco de dados...");
  }

  query(sql: string) {
    return this.connection.query(sql);
  }
}

// Aplicação
const app = new Hono();
const container = IoC.ns("default");

// Registro direto
IoC.register({
  token: "logger",
  useClass: Logger,
  lifeTime: LifeTime.SINGLETON,
});

IoC.register({
  token: "database",
  useClass: DatabaseService,
  lifeTime: LifeTime.SINGLETON,
});

// Inicialização (chama lifecycle hooks)
await container.bootstrapServices();

// Middleware
app.use(contextIoC(container));

// Rotas
app.get("/", (c) => {
  const logger = c.get("use")<Logger>("logger");
  const db = c.get("use")<DatabaseService>("database");

  logger.log("Requisição recebida");
  const data = db.query("SELECT * FROM users");

  return c.json(data);
});

// Cleanup (chama lifecycle hooks)
process.on("SIGINT", async () => {
  await container.shutdownServices();
  process.exit(0);
});
```

## Contribuições

Contribuições são bem-vindas! Por favor, abra uma issue ou envie um pull
request.