# A via de consulta wasm

A maioria das consultas roda do nosso lado: você envia SQL, nós autorizamos, executamos, e devolvemos linhas tipadas. **A via wasm inverte isso.** Você pede um *plano* em vez de linhas — Parquet pré-assinado, um arquivo por conjunto de dados, mais o SQL a rodar sobre ele — e a consulta executa no navegador do visitante através do DuckDB-WASM.

Esta é a via por trás de um painel que não precisa de **credencial nenhuma**: sem chave de API, sem token de incorporação, sem servidor seu. É também a via com o trade-off mais afiado de toda a plataforma, e o trade-off não está escondido em lugar nenhum — é o mesmo fato que "nenhum segredo para vazar", lido do outro lado.

## Como pedi-la[​](#como-pedi-la "Link direto para Como pedi-la")

Um argumento na chamada de consulta seleciona a via.

```
from vibedasher import Vibedasher



client = Vibedasher(api_key="...")



result = client.query(sql="SELECT region AS dimension, SUM(price) AS value FROM sales GROUP BY 1", type="wasm")



if result.mode == "wasm":

    run_locally(result.plan)        # plan.sql + plan.sources

else:

    print(result.rows, result.fallback_reason)
```

Por MCP é a mesma escolha, escrita `run_query(sql, type='wasm')`.

`type` é `"backend"` (o padrão) ou `"wasm"`. Tudo acima da escolha da via permanece igual: a autorização, a validação apenas por alias e a injeção de filtros continuam acontecendo no servidor, **antes** da checagem de capacidade. Pedir wasm não pula nenhum portão. Um conjunto de dados que a chave não pode alcançar é rejeitado antes de qualquer extrato ser materializado ou qualquer URL ser assinada.

## Ramifique sobre `mode`, nunca sobre o que você pediu[​](#ramifique-sobre-mode-nunca-sobre-o-que-você-pediu "Link direto para ramifique-sobre-mode-nunca-sobre-o-que-você-pediu")

**Pedir wasm não garante recebê-lo.** Se algum conjunto de dados participante não for capaz de wasm, a consulta executa no servidor exatamente como de costume — e diz isso em vez de falhar.

| Campo                                                                                            | Significado                                                                                |
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| `mode`                                                                                           | O que **realmente** rodou: `"backend"` ou `"wasm"`. Este é o campo sobre o qual ramificar. |
| `fallbackReason`                                                                                 | Definido apenas quando `type="wasm"` foi pedido e recusado, nomeando **por quê**.          |
| `plan`                                                                                           | Definido apenas quando `mode == "wasm"`.                                                   |
| `columns` / `rows`                                                                               | Vazios quando `mode == "wasm"` — nada foi computado.                                       |
| A tabela traz a grafia do protocolo. O SDK Python os expõe em snake\_case                        |                                                                                            |
| (`result.fallback_reason`), o cliente TypeScript em camelCase, e os *parâmetros* das ferramentas |                                                                                            |
| MCP são snake\_case em todos os casos — portanto `type` é `type` em qualquer lugar, mas          |                                                                                            |
| `fallbackReason` vira `fallback_reason` em Python.                                               |                                                                                            |

Um fallback silencioso seria o pior comportamento possível aqui: seu código receberia linhas, acreditaria estar na via sem credenciais, e enviaria um build que precisa de uma credencial que ele não tem. Então o fallback nunca é silencioso, e a razão é sempre nomeada.

## O que um plano contém[​](#o-que-um-plano-contém "Link direto para O que um plano contém")

```
{

  "mode": "wasm",

  "plan": {

    "sql": "SELECT region AS dimension, SUM(price) AS value FROM sales GROUP BY 1",

    "sources": [

      { "alias": "sales", "datasetId": 12, "url": "https://...", "bytes": 4194304 }

    ]

  }

}
```

* **`plan.sql` é o SQL *injetado*** — filtros aplicados, parâmetros já escapados como literais. Não é o seu SQL bruto, e esse é o ponto: as duas vias computam resultados idênticos, então um painel não pode significar uma coisa servida pelo nosso motor e outra coisa servida pelo navegador.
* **`sources[].alias` é o `cleanSQLName` do conjunto de dados** — exatamente o nome de tabela que o `plan.sql` referencia, e o nome sob o qual o arquivo Parquet deve ser registrado localmente. O arquivo já carrega nomes de coluna limpos.
* **`sources[].url` é pré-assinada e somente leitura.** Ela é baixável de qualquer origem, e o endpoint do plano também.

Executar um plano é: buscar cada `url`, registrá-la no DuckDB-WASM como uma view chamada `alias`, rodar o `plan.sql`. Um painel ejetado nesta via traz um runner que faz exatamente isso e nada mais; sua única dependência é o `@duckdb/duckdb-wasm`, que está no npm.

## Você tem de pedir o extrato antes de ele existir[​](#you-have-to-ask-for-the-extract-before-it-exists "Link direto para Você tem de pedir o extrato antes de ele existir")

Esta é a única regra de sequência, e pulá-la produz um loop que nunca termina.

**Pedir `type="wasm"` é a única coisa que constrói o extrato wasm de um conjunto de dados.** O extrato é materializado de forma preguiçosa, por esse pedido — ou pelo caminho de atualização depois que um conjunto de dados fica pronto — e em nenhum outro lugar.

Então se [a ejeção](/pt-BR/guides/eject-your-dashboard.md) relatar `dataLane.id == "token"` com a razão `no-wasm-extract`, a correção é rodar **uma consulta por conjunto de dados nomeado** com `type="wasm"` — qualquer `SELECT` trivial contra o alias serve — e então ejetar de novo. Um plano devolvido significa que o extrato agora existe; para esse propósito você pode ignorar completamente o conteúdo do plano.

**Publicar não assa um extrato.** "Republicar e reejetar" vai rodar em círculos para sempre.

## A via anônima, e por que ela não está na referência da API[​](#a-via-anônima-e-por-que-ela-não-está-na-referência-da-api "Link direto para A via anônima, e por que ela não está na referência da API")

A via acima precisa de uma chave de API, porque é você construindo um painel. O que o navegador de um *visitante* chama é um endpoint diferente, não autenticado: o **plano wasm** da visualização, em

```
GET /v1/public/viz/{account_slug}/{viz_slug}/wasm-plan
```

Ele está deliberadamente **ausente da referência de API gerada e dos dois SDKs**, e essa ausência é uma decisão de projeto, não uma omissão. A rota é anônima e voltada ao navegador; não existe chamada por chave de API para ela, então ela não tem lugar em um SDK de chave de API. Ela é excluída no nível do framework, e é por isso que você não vai encontrá-la navegando [a referência](/pt-BR/reference/vibedasher-sdk-api.md) — a única descrição dela é esta página e o runner que a chama.

Suas proteções são **idênticas** às da rota de cache anônima ao lado dela: a conta precisa existir e permitir painéis públicos, e a visualização precisa ser pública **e** publicada. Um painel privado ou protegido responde 404 aqui. Nada nisso é um padrão do lado do cliente que você possa sobrescrever.

Ela retorna `{ sources: [...], unavailable: [...] }`. Um conjunto de dados que não é capaz de wasm é relatado em **`unavailable` com uma razão** em vez de ser silenciosamente removido de `sources` — porque um cliente que recebesse uma lista `sources` curta e nenhuma explicação renderizaria um painel parcial e o daria por pronto.

### Por que um painel protegido nunca pode ser servido desta forma[​](#por-que-um-painel-protegido-nunca-pode-ser-servido-desta-forma "Link direto para Por que um painel protegido nunca pode ser servido desta forma")

As linhas de um painel protegido são delimitadas por visualizador reescrevendo o SQL no servidor. Um arquivo Parquet pré-assinado é **a tabela inteira**. Não há maneira de honrar um predicado de linha que o cliente simplesmente poderia se recusar a rodar — então a via não está "ainda não implementada" para painéis protegidos; ela é estruturalmente excluída, e sempre será.

## O que você está sacrificando[​](#o-que-você-está-sacrificando "Link direto para O que você está sacrificando")

Declare isso antes de construir, porque é a metade que você não pode mudar depois.

**O extrato é indexado por conjunto de dados e snapshot, não pela sua consulta.** O mesmo `SELECT` com e sem `WHERE` devolve o arquivo idêntico. Seu predicado não o encolhe. Consequências, todas elas a mesma consequência:

* Ele contém **todas as linhas** de cada conjunto de dados que o painel lê.
* É **um arquivo, o mesmo arquivo**, para todos que abrem a página.
* A página não emite consulta ao servidor, então **nada no caminho poderia dar a um visualizador menos linhas do que a outro**.
* Um filtro aplicado no navegador muda o que é **desenhado**, nunca o que foi **entregue** — e os bytes estão na aba de rede de qualquer forma.
* Ele serve o **snapshot publicado**, não uma consulta viva. Atualizar o que ele mostra significa republicar.

Em troca: nenhuma chave para vazar, nenhum token para renovar, nenhum backend seu para rodar ou pagar, nenhum limite de taxa na frente da página, e um painel que continua renderizando a partir de uma hospedagem estática.

**Também vale saber: o limite de linhas não se aplica aqui.** A via do lado do servidor para em 100.000 linhas e define `truncated`, o que torna silenciosamente errado todo total calculado a partir do prefixo. O Parquet da via wasm é o conjunto de dados inteiro por snapshot, precisamente porque é indexado por conjunto de dados em vez de pelo seu resultado — então ela é também a resposta honesta para um conjunto de resultados genuinamente grande, não só para um problema de credencial.

## Quando escolher outra coisa[​](#quando-escolher-outra-coisa "Link direto para Quando escolher outra coisa")

| Você precisa de                                       | Via                                                                                                               |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| linhas que você vai ler e sobre as quais raciocinar   | `run_query` / `client.query` — a via backend padrão                                                               |
| muitas linhas em disco                                | `run_query_to_file(output_path=...)`                                                                              |
| um painel público sem credencial e sem backend        | **wasm**                                                                                                          |
| que visualizadores diferentes vejam linhas diferentes | um **token de incorporação com escopo** carregando `filters`, ou código de servidor confiável que restringe o SQL |
| dados ao vivo em vez de um snapshot publicado         | a via backend, atrás de um servidor que você controla                                                             |

Um cliente com token com escopo **recusa** `type="wasm"` de saída em vez de rodar silenciosamente no servidor: quem pedisse wasm e recebesse linhas de qualquer jeito não teria como perceber. Essa recusa é o mesmo princípio do `fallbackReason` — uma mudança de via nunca deve ser invisível.

Para as duas ligações que *podem* delimitar um visualizador, veja [Filtragem em nível de linha](/pt-BR/guides/who-can-see-what.md#row-level-filtering-the-embed-lane-and-only-the-embed-lane).
