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

- Área: Operação
- URL humana: https://ninenity.vercel.app/doc/multi-guild
- URL Markdown: https://ninenity.vercel.app/doc/markdown/multi-guild
- Pacotes e conceitos: Context isolation, Guild resources, Scoped runtime

## O problema que o escopo resolve

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

- **Configuração**: Cada guild possui canais, cargos e variáveis próprios.
- **Concorrência**: Filas lógicas recebem a guild na chave interna quando há contexto.
- **Interações**: Prompts e paginações validam autor, guild e Client responsável.

> **Com contexto, a API continua simples:** Dentro de comandos, eventos, callbacks, modais e tasks por guild, continue usando `Client.appConfig()` sem argumentos. O runtime mantém a guild ativa durante a execução.

## Contexto automático

### mesma função em qualquer guild

```text

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.

### sincronização externa

```text

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 ?? {}
    })
  }
}
```


> **Nunca use a primeira guild do cache como fallback:** `Client.guilds.cache.first()` torna o comportamento dependente da ordem de conexão. Se a operação é por guild, o ID deve vir do contexto, argumento ou dado persistido.

## Filas e cooldowns

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

### fila por recurso e guild

```text

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.

### factory escopada

```text

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.
