Documentação/Operação
OPERAÇÃO / MULTI-GUILD

Multi-guild

Mantenha configuração, recursos, filas e interações independentes quando o mesmo bot atende vários servidores.

Context isolationGuild resourcesScoped runtime

O problema que o escopo resolve#

Sem isolamento, duas guilds podem disputar a mesma configuração, fila ou instância de componente.

C

Configuração

Cada guild possui canais, cargos e variáveis próprios.

Q

Concorrência

Filas lógicas recebem a guild na chave interna quando há contexto.

I

Interações

Prompts e paginações validam autor, guild e Client responsável.

Contexto automático#

TSmesma função em qualquer guild
async function sendSupportPanel() {  const config = Client.appConfig()  const channel = Client.getAppChannels().support  const staff = Client.getAppRoles().staff  if (!channel) return  await sendPanel(channel.Id, {    title: config.variables?.tickets?.title ?? 'Suporte',    staffRoleId: staff?.Id  })}Client.on(Events.GuildMemberAdd, {  execute: async member => {    // O evento ativa o contexto de member.guild.id.    await sendSupportPanel()  }})

A função sendSupportPanel não recebe guildId, porque foi chamada durante um evento contextualizado. Se a mesma função também rodar em um script administrativo sem contexto, passe o ID explicitamente em sua assinatura.

Contexto explícito#

Jobs externos, scripts e callbacks fora do runtime precisam indicar a guild que desejam ler.

TSsincronização externa
async function synchronizeAllGuilds() {  for (const guild of Client.guilds.cache.values()) {    const config = Client.appConfig(guild.id)    const channels = Client.getAppChannels(guild.id)    const roles = Client.getAppRoles(guild.id)    await syncGuild({      guildId: guild.id,      auditChannelId: channels.audit?.Id,      staffRoleId: roles.staff?.Id,      options: config.variables?.sync ?? {}    })  }}

Filas e cooldowns#

O Core acrescenta o escopo quando a chamada ocorre dentro de uma execução contextualizada.

TSfila por recurso e guild
Client.slash({  data: new SlashCommandBuilder()    .setName('sync')    .setDescription('Sincroniza o painel.'),  cooldown: '15s',  execute: async interaction => {    await interaction.deferReply({ flags: 64 })    await Client.queue('panel-sync', async () => {      await syncPanel(interaction.guildId)    })    await interaction.editReply('Painel sincronizado.')  }})

Duas execuções de panel-sync na mesma guild entram na mesma fila. A mesma chave em outra guild segue independentemente. O cooldown de comando também considera a guild para não bloquear o usuário em todos os servidores.

Prompts e paginações escopados#

Cada solicitação registra uma instância temporária vinculada ao autor e à guild.

TSfactory escopada
Client.registerPaginator('members-pages', async context => {  const members = context.guildId    ? await memberRepository.list(context.guildId)    : []  return Paginator.dynamic({    id: 'members-pages',    ownerId: context.ownerId,    guildId: context.guildId,    data: members,    pageSize: 10,    render: renderMembersPage  })})
  • Outro usuário não consegue navegar pela instância aberta pelo autor.
  • Um componente enviado em uma guild não é resolvido pela instância de outra guild.
  • Dois Clients no mesmo processo usam namespaces de runtime separados.
  • Dados retornados por uma factory devem ser consultados usando context.guildId.

Checklist multi-guild#

  • Toda tabela ou coleção persistente inclui guildId quando os dados pertencem ao servidor.
  • Caches criados pela aplicação usam Map<guildId, value> ou uma chave composta.
  • IDs de canais e cargos vêm de aliases resolvidos, não de constantes globais.
  • Factories de prompts e paginações usam context.guildId.
  • Tasks de escopo guild não gravam o resultado em uma variável global única.
  • Logs de operação incluem a guild quando isso ajuda a diagnosticar a origem.