# 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[​](#cómo-pedirlo "Enlace directo al 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[​](#ramifica-sobre-mode-nunca-sobre-lo-que-pediste "Enlace directo al 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[​](#qué-contiene-un-plan "Enlace directo al 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.sql` es 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[].alias` es el `cleanSQLName` del conjunto de datos** — exactamente el nombre de tabla que `plan.sql` referencia, y el nombre bajo el que el archivo Parquet debe registrarse en local. El archivo ya trae nombres de columna limpios.
* **`sources[].url` está 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[​](#you-have-to-ask-for-the-extract-before-it-exists "Enlace directo al 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](/es/guides/eject-your-dashboard.md) 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-anónimo-y-por-qué-no-está-en-la-referencia-de-la-api "Enlace directo al 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](/es/reference/vibedasher-sdk-api.md) — 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[​](#por-qué-un-panel-protegido-nunca-puede-servirse-de-esta-forma "Enlace directo al 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[​](#qué-estás-sacrificando "Enlace directo al 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[​](#cuándo-elegir-otra-cosa "Enlace directo al 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](/es/guides/who-can-see-what.md#row-level-filtering-the-embed-lane-and-only-the-embed-lane).
