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
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
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
{
"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é ocleanSQLNamedo conjunto de dados — exatamente o nome de tabela que oplan.sqlreferencia, 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
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 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 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 — 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
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
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
| 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.