El carril de consultas wasm
La mayoría de las consultas se ejecutan de nuestro lado: envías SQL, lo autorizamos, lo ejecutamos, y devolvemos filas tipadas. El carril wasm invierte eso. Pides un plan en lugar de filas — Parquet prefirmado, un archivo por conjunto de datos, más el SQL que hay que ejecutar sobre él — y la consulta se ejecuta en el navegador del visitante a través de DuckDB-WASM.
Este es el carril detrás de un panel que no necesita ninguna credencial: sin clave de API, sin token de incrustación, sin servidor propio. Es también el carril con el compromiso más afilado de toda la plataforma, y ese compromiso no está escondido en ningún sitio — es el mismo hecho que «ningún secreto que filtrar», leído del otro lado.
Cómo pedirlo
Un argumento en la llamada de consulta selecciona el carril.
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 es la misma elección, escrita run_query(sql, type='wasm').
type es "backend" (el valor por defecto) o "wasm". Todo lo que está por encima de la
elección del carril no cambia: la autorización, la validación solo por alias y la inyección de
filtros siguen ocurriendo en el servidor, antes de la comprobación de capacidad. Pedir wasm
no se salta ninguna puerta. Un conjunto de datos que la clave no puede alcanzar se rechaza antes
de que se materialice ningún extracto o se firme ninguna URL.
Ramifica sobre mode, nunca sobre lo que pediste
Pedir wasm no garantiza obtenerlo. Si algún conjunto de datos participante no es apto para wasm, la consulta se ejecuta en el servidor exactamente como siempre — y lo dice en lugar de fallar.
| Campo | Significado |
|---|---|
mode | Lo que realmente se ejecutó: "backend" o "wasm". Este es el campo sobre el que ramificar. |
fallbackReason | Se define solo cuando se pidió type="wasm" y se rechazó, nombrando por qué. |
plan | Se define solo cuando mode == "wasm". |
columns / rows | Vacíos cuando mode == "wasm" — no se computó nada. |
| La tabla da la ortografía del protocolo. El SDK de Python los expone en snake_case | |
(result.fallback_reason), el cliente TypeScript en camelCase, y los parámetros de las | |
herramientas MCP van en snake_case en todas partes — así que type es type en cualquier caso, | |
pero fallbackReason se escribe fallback_reason en Python. |
Un repliegue silencioso sería el peor comportamiento posible aquí: tu código recibiría filas, creería estar en el carril sin credenciales, y enviaría un build que necesita una credencial que no tiene. Así que el repliegue nunca es silencioso, y la razón siempre se nombra.
Qué contiene un plan
{
"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.sqles el SQL inyectado — filtros aplicados, parámetros ya escapados como literales. No es tu SQL crudo, y ahí está el punto: ambos carriles computan resultados idénticos, así que un panel no puede significar una cosa servida desde nuestro motor y otra cosa servida desde el navegador.sources[].aliases elcleanSQLNamedel conjunto de datos — exactamente el nombre de tabla queplan.sqlreferencia, y el nombre bajo el que el archivo Parquet debe registrarse en local. El archivo ya trae nombres de columna limpios.sources[].urlestá prefirmada y es de solo lectura. Es descargable desde cualquier origen, y el endpoint del plan también lo es.
Ejecutar un plan es: descargar cada url, registrarla en DuckDB-WASM como una vista llamada
alias, ejecutar plan.sql. Un panel expulsado sobre este carril trae un runner que hace
exactamente eso y nada más; su única dependencia es @duckdb/duckdb-wasm, que está en npm.
Tienes que pedir el extracto antes de que exista
Esta es la única regla de secuencia, y saltársela produce un bucle que no termina nunca.
Pedir type="wasm" es lo único que construye el extracto wasm de un conjunto de datos. El
extracto se materializa de forma perezosa, por esa petición — o por la vía de actualización
después de que un conjunto de datos queda listo — y en ningún otro sitio.
Así que si la expulsión informa de dataLane.id == "token" con la
razón no-wasm-extract, el arreglo es ejecutar una consulta por conjunto de datos nombrado con
type="wasm" — cualquier SELECT trivial contra el alias servirá — y luego expulsar de nuevo. Un
plan devuelto significa que el extracto ya existe; para este propósito puedes ignorar por completo
el contenido del plan.
Publicar no cuece un extracto. «Volver a publicar y volver a expulsar» dará vueltas para siempre.
El carril anónimo, y por qué no está en la referencia de la API
El carril de arriba necesita una clave de API, porque eres tú construyendo un panel. Lo que llama el navegador de un visitante es un endpoint distinto, sin autenticar: el plan wasm de la visualización, en
GET /v1/public/viz/{account_slug}/{viz_slug}/wasm-plan
Está deliberadamente ausente de la referencia de API generada y de los dos SDK, y esa ausencia es una decisión de diseño más que una omisión. La ruta es anónima y orientada al navegador; no hay llamada con clave de API para ella, así que no tiene sitio en un SDK de clave de API. Está excluida a nivel del framework, que es por lo que no la encontrarás navegando la referencia — la única descripción que existe de ella es esta página y el runner que la llama.
Sus guardas son idénticas a las de la ruta de caché anónima que está a su lado: la cuenta debe existir y permitir paneles públicos, y la visualización debe ser pública y estar publicada. Un panel privado o protegido responde 404 aquí. Nada de ello es un valor por defecto del lado cliente que puedas sobrescribir.
Devuelve { sources: [...], unavailable: [...] }. Un conjunto de datos que no es apto para wasm
se reporta en unavailable con una razón en lugar de desaparecer silenciosamente de
sources — porque un cliente que recibiera una lista sources corta y ninguna explicación
renderizaría un panel parcial y lo daría por terminado.
Por qué un panel protegido nunca puede servirse de esta forma
Las filas de un panel protegido se acotan por visor reescribiendo el SQL en el servidor. Un archivo Parquet prefirmado es la tabla entera. No hay forma de honrar un predicado de filas que el cliente simplemente podría negarse a ejecutar — así que el carril no está «aún no implementado» para paneles protegidos; está estructuralmente excluido, y lo estará siempre.
Qué estás sacrificando
Dilo antes de construir, porque es la mitad que no puedes cambiar después.
El extracto está indexado por conjunto de datos e instantánea, no por tu consulta. El mismo
SELECT con y sin WHERE devuelve el archivo idéntico. Tu predicado no lo encoge. Consecuencias,
todas ellas la misma consecuencia:
- Contiene todas las filas de cada conjunto de datos que lee el panel.
- Es un archivo, el mismo archivo, para todo el que abra la página.
- La página no emite ninguna consulta al servidor, así que nada en la ruta podría dar a un visor menos filas que a otro.
- Un filtro aplicado en el navegador cambia lo que se dibuja, nunca lo que se entregó — y los bytes están en la pestaña de red en cualquier caso.
- Sirve la instantánea publicada, no una consulta viva. Refrescar lo que muestra significa volver a publicar.
A cambio: ninguna clave que filtrar, ningún token que renovar, ningún backend propio que ejecutar o pagar, ningún límite de tasa delante de la página, y un panel que sigue renderizando desde un host estático.
También vale saberlo: el límite de filas no se aplica aquí. El carril del lado servidor se
detiene en 100.000 filas y pone truncated, lo que hace silenciosamente erróneo cualquier total
calculado desde el prefijo. El Parquet del carril wasm es el conjunto de datos completo por
instantánea, precisamente porque está indexado por conjunto de datos y no por tu resultado — así
que es también la respuesta honesta a un conjunto de resultados genuinamente grande, no solo a un
problema de credenciales.
Cuándo elegir otra cosa
| Necesitas | Carril |
|---|---|
| filas que vas a leer y sobre las que razonar | run_query / client.query — el carril backend por defecto |
| muchas filas en disco | run_query_to_file(output_path=...) |
| un panel público sin credencial y sin backend | wasm |
| que visores distintos vean filas distintas | un token de incrustación con alcance limitado que lleve filters, o código de servidor confiable que acote el SQL |
| datos vivos en lugar de una instantánea publicada | el carril backend, detrás de un servidor que tú controlas |
Un cliente con token de alcance limitado rechaza type="wasm" de plano en lugar de ejecutarse
calladamente en el servidor: quien pidiera wasm y obtuviera filas de todos modos no tendría forma
de notarlo. Ese rechazo es el mismo principio que fallbackReason — un cambio de carril nunca debe
ser invisible.
Para los dos montajes que sí pueden acotar a un visor, mira Filtrado a nivel de fila.