Por que o bridge existe
BotBridge é a camada de abstração entre controllers HTTP e operações do Discord. Controllers devem depender de métodos de BotBridge, não de internals brutos do discord.js como client.guilds.cache ou guild.members.ban(). Isso mantém o comportamento das rotas estável mesmo quando a topologia de implantação muda de um processo único para workers sharded, porque o código do controller não muda.
Sem o bridge, sair do modo direto para o modo sharded exigiria reescrever todo controller que interage com o Discord. O padrão bridge isola essa diferença em duas classes concretas, DirectBotBridge e ShardedBotBridge, ambas implementando o mesmo contrato abstrato. BridgeFactory escolhe a implementação correta na inicialização do servidor e a injeta no mapa de dependências da DI, então controllers e services podem declarar BotBridge como parâmetro de construtor e receber automaticamente a implementação certa.
O bridge também força a serialização na fronteira. Objetos do discord.js contêm referências circulares, caches vivos e métodos que não são seguros para serializar em JSON. O bridge retorna objetos tipados simples como SerializedGuild, SerializedMember e SerializedBan, seguros para transporte HTTP.
Seleção do modo de runtime
BridgeFactory.create(client, manager) escolhe a implementação de bridge com base na referência de runtime disponível. A factory segue uma precedência estrita: ShardingManager tem prioridade sobre Client. Se nenhum dos dois for fornecido, a factory lança um erro na inicialização, impedindo que o servidor rode sem conectividade com o Discord.
| Modo | Implementação | Como acessa o Discord | Quando usar |
|---|---|---|---|
| Direto | DirectBotBridge | Usa a instância local de Client do Discord. Lê do cache de guilds e chama métodos da API diretamente no mesmo processo. | Bots em processo único ou ambientes de desenvolvimento em que sharding é desnecessário. |
| Sharded | ShardedBotBridge | Usa | Bots de produção em vários shards, em que o servidor HTTP roda no processo pai sem um Client local. |
Contrato de capacidades do BotBridge
A classe abstrata BotBridge define 12 métodos organizados em três grupos de operação. Toda implementação concreta precisa cumprir todos esses 12 métodos, garantindo que controllers tenham uma API completa e consistente independentemente do modo de runtime.
Métodos de leitura retornam objetos serializados ou null. Métodos de ação retornam flags booleanas de sucesso. Todo método do bridge envolve seu corpo em um try/catch: em caso de falha, métodos de leitura retornam null, ou array vazio no caso de getBans, e métodos de ação retornam false. Isso significa que controllers nunca precisam tratar exceções inesperadas vindas de chamadas ao bridge.
Como o bridge sharded funciona
ShardedBotBridge usa manager.broadcastEval() para executar um closure em cada shard do cluster. O closure recebe o Client local daquele shard e um objeto de contexto serializado com os parâmetros, como guild ID e user ID. Só o shard que controla a guild de destino vai encontrá-la em seu cache, então os demais retornam null ou false.
Para operações de leitura, o bridge coleta resultados de todos os shards e retorna o primeiro valor não nulo usando .find(r => r !== null). Para operações de ação, retorna true se qualquer shard tiver retornado true, usando .some(r => r === true). Esse padrão de fan-out faz com que toda operação chegue ao shard correto sem exigir que o servidor HTTP conheça a topologia dos shards nem o mapeamento de guild para shard.
O custo aqui é latência: toda chamada ao bridge no modo sharded paga o preço da comunicação entre processos com todos os shards. Para bots com muitos shards, considere cachear mapeamentos de guild para shard se endpoints com muita leitura virarem gargalo.
Controller consumindo o bridge
import { HttpController, HttpGet, HttpPost, HttpParam, HttpBody, HttpStatus } from '@spraxium/http';
import { BotBridge, NotFoundError } from '@spraxium/http';
import { BanMemberDto } from '../dto/ban-member.dto';
@HttpController('/guilds/:guildId/moderation')
export class ModerationHttpController {
constructor(private readonly bridge: BotBridge) {}
@HttpGet('/bans')
async listBans(@HttpParam('guildId') guildId: string) {
const bans = await this.bridge.getBans(guildId);
return { ok: true, guildId, bans, count: bans.length };
}
@HttpGet('/members/:userId')
async getMember(
@HttpParam('guildId') guildId: string,
@HttpParam('userId') userId: string,
) {
const member = await this.bridge.getMember(guildId, userId);
if (!member) throw new NotFoundError('Member not found in guild');
return { ok: true, member };
}
@HttpPost('/bans/:userId')
@HttpStatus(201)
async banMember(
@HttpParam('guildId') guildId: string,
@HttpParam('userId') userId: string,
@HttpBody(BanMemberDto) body: BanMemberDto,
) {
const success = await this.bridge.banMember(
guildId,
userId,
body.reason,
{ deleteMessageSeconds: body.deleteMessageSeconds },
);
return { ok: success, guildId, userId };
}
}Modelo de serialização
O bridge usa três classes serializer para converter objetos de runtime do discord.js em payloads tipados simples, seguros para transporte JSON. Esses serializers removem referências circulares, convertem datas para strings ISO e extraem apenas os campos de que clientes externos precisam. Os tipos serializados funcionam como fronteira estável da API: controllers retornam esses tipos, e clientes os consomem.
| Serializer | Entrada | Campos de saída |
|---|---|---|
| GuildSerializer | discord.js Guild | id, name, icon, memberCount, ownerId, createdAt (string ISO), features (array de strings). |
| MemberSerializer | discord.js GuildMember | id, username, displayName, discriminator, avatar, bot, joinedAt, createdAt, roles (array de SerializedRole com id/name/color/position/permissions/managed/mentionable), pending, communicationDisabledUntil. |
| BanSerializer | discord.js GuildBan | user (SerializedUser aninhado com id/username/discriminator/avatar/bot/createdAt), reason (string ou null). |
No modo direto, serializers rodam no mesmo processo do client do Discord. No modo sharded, o closure de broadcastEval faz a serialização inline no shard que controla a guild, retornando objetos simples que atravessam a fronteira de IPC com segurança. Os dois caminhos produzem o mesmo formato de saída, então o código do controller é idêntico em qualquer modo.
Checklist de design para endpoints com bridge
Projete para transparência de modo
Mantenha a lógica do controller idêntica em implantações diretas e sharded,
dependendo apenas de métodos de BotBridge. A seleção do modo deve
continuar sendo uma preocupação de configuração em defineHttp({ sharding }),
não do controller. Nunca importe DirectBotBridge ou ShardedBotBridge
diretamente no código do controller.
Retorne apenas payloads serializados
Trate os tipos de saída dos serializers como a fronteira da sua API. Nunca retorne objetos de runtime do discord.js a partir de handlers, porque eles contêm referências circulares e caches vivos que quebram a serialização JSON. Se você precisar de campos não cobertos pelos serializers padrão, crie um serializer customizado em vez de expor objetos brutos do discord.js.
Trate leituras anuláveis explicitamente
Operações de leitura podem retornar null quando entidades estão ausentes
ou indisponíveis no estado atual do runtime. Converta resultados null em
respostas HTTP claras com base no contrato da sua rota. Uma guild null
normalmente vira NotFoundError(404), enquanto um membro null pode pedir
NotFoundError ou uma resposta vazia filtrada, dependendo da semântica do
endpoint.
Mantenha mutações idempotentes quando possível
Métodos de ação retornam booleanos e podem rodar em contextos distribuídos,
onde retries acontecem. A semântica do endpoint deve tolerar retries sem
efeitos colaterais. Por exemplo, banir um usuário já banido retorna false,
mas não cria conflito, e o controller ainda pode reportar o resultado da
operação sem lançar erro.
Valide payloads de mutação com DTOs
Use @HttpBody(DtoClass) com constraints de class-validator em todo endpoint de mutação. Ações do bridge como banMember e timeoutMember recebem parâmetros que devem ser validados antes de alcançar a camada de bridge. A validação por DTO captura entrada malformada cedo e produz respostas de erro estruturadas automaticamente.
Métodos do bridge nunca lançam exceções em caso de falha
Todos os métodos do bridge envolvem as operações internamente em try/catch. Métodos de leitura retornam null em caso de falha, e métodos de ação retornam false. Desenhe o tratamento de erro do controller em torno desses valores de retorno, e não esperando exceções lançadas por chamadas ao bridge.