# La voie de requête wasm

La plupart des requêtes s'exécutent de notre côté : vous envoyez du SQL, nous l'autorisons, l'exécutons, et renvoyons des lignes typées. **La voie wasm inverse cela.** Vous demandez un *plan* au lieu de lignes — du Parquet présigné, un fichier par jeu de données, plus le SQL à exécuter dessus — et la requête s'exécute dans le navigateur du visiteur via DuckDB-WASM.

C'est la voie derrière un tableau de bord qui n'a besoin **d'aucun identifiant** : pas de clé d'API, pas de jeton d'intégration, pas de serveur à vous. C'est aussi la voie au compromis le plus tranché de toute la plateforme, et ce compromis n'est caché nulle part — c'est le même fait que « aucun secret à laisser fuir », lu de l'autre côté.

## Comment la demander[​](#comment-la-demander "Lien direct vers Comment la demander")

Un argument sur l'appel de requête sélectionne la voie.

```
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)
```

Via MCP c'est le même choix, écrit `run_query(sql, type='wasm')`.

`type` vaut `"backend"` (la valeur par défaut) ou `"wasm"`. Tout ce qui est au-dessus du choix de voie est inchangé : l'autorisation, la validation par alias uniquement et l'injection de filtres se font toutes côté serveur, **avant** la vérification de capacité. Demander wasm ne saute aucun contrôle. Un jeu de données que la clé ne peut pas atteindre est rejeté avant qu'un extrait ne soit matérialisé ou qu'une URL ne soit signée.

## Branchez sur `mode`, jamais sur ce que vous avez demandé[​](#branchez-sur-mode-jamais-sur-ce-que-vous-avez-demandé "Lien direct vers branchez-sur-mode-jamais-sur-ce-que-vous-avez-demandé")

**Demander wasm ne garantit pas de l'obtenir.** Si l'un des jeux de données participants n'est pas compatible wasm, la requête s'exécute côté serveur exactement comme d'habitude — et le dit plutôt que d'échouer.

| Champ                                                                                               | Signification                                                                                      |
| --------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `mode`                                                                                              | Ce qui s'est **réellement** exécuté : `"backend"` ou `"wasm"`. C'est le champ sur lequel brancher. |
| `fallbackReason`                                                                                    | Défini uniquement quand `type="wasm"` a été demandé et refusé, en nommant **pourquoi**.            |
| `plan`                                                                                              | Défini uniquement quand `mode == "wasm"`.                                                          |
| `columns` / `rows`                                                                                  | Vides quand `mode == "wasm"` — rien n'a été calculé.                                               |
| Le tableau donne l'orthographe du protocole. Le SDK Python les expose en snake\_case                |                                                                                                    |
| (`result.fallback_reason`), le client TypeScript en camelCase, et les *paramètres* des outils MCP   |                                                                                                    |
| sont en snake\_case partout — donc `type` reste `type` en toute circonstance, mais `fallbackReason` |                                                                                                    |
| s'écrit `fallback_reason` en Python.                                                                |                                                                                                    |

Un repli silencieux serait le pire comportement possible ici : votre code recevrait des lignes, croirait être sur la voie sans identifiants, et expédierait un build qui a besoin d'un identifiant qu'il n'a pas. Donc le repli n'est jamais silencieux, et la raison est toujours nommée.

## Ce que contient un plan[​](#ce-que-contient-un-plan "Lien direct vers Ce que contient 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` est le SQL *injecté*** — filtres appliqués, paramètres déjà échappés en littéraux. Ce n'est pas votre SQL brut, et c'est là tout le point : les deux voies calculent des résultats identiques, donc un tableau de bord ne peut pas signifier une chose servie depuis notre moteur et une autre chose servie depuis le navigateur.
* **`sources[].alias` est le `cleanSQLName` du jeu de données** — exactement le nom de table que `plan.sql` référence, et le nom sous lequel le fichier Parquet doit être enregistré localement. Le fichier porte déjà des noms de colonnes propres.
* **`sources[].url` est présignée et en lecture seule.** Elle est récupérable depuis n'importe quelle origine, et l'endpoint du plan l'est aussi.

Exécuter un plan, c'est : récupérer chaque `url`, l'enregistrer dans DuckDB-WASM comme une vue nommée `alias`, exécuter `plan.sql`. Un tableau de bord éjecté sur cette voie embarque un runner qui fait exactement cela et rien d'autre ; sa seule dépendance est `@duckdb/duckdb-wasm`, qui est sur npm.

## Vous devez demander l'extrait avant qu'il n'existe[​](#you-have-to-ask-for-the-extract-before-it-exists "Lien direct vers Vous devez demander l'extrait avant qu'il n'existe")

C'est l'unique règle de séquencement, et la sauter produit une boucle qui ne termine jamais.

**Demander `type="wasm"` est la seule chose qui construit l'extrait wasm d'un jeu de données.** L'extrait est matérialisé paresseusement, par cette demande — ou par le chemin d'actualisation après qu'un jeu de données devient prêt — et nulle part ailleurs.

Donc si [l'éjection](/fr/guides/eject-your-dashboard.md) rapporte `dataLane.id == "token"` avec la raison `no-wasm-extract`, le correctif est d'exécuter **une requête par jeu de données nommé** avec `type="wasm"` — n'importe quel `SELECT` trivial contre l'alias suffira — puis d'éjecter à nouveau. Un plan renvoyé signifie que l'extrait existe désormais ; pour cet usage vous pouvez ignorer entièrement le contenu du plan.

**Publier ne cuit pas d'extrait.** « Republier et rééjecter » bouclera indéfiniment.

## La voie anonyme, et pourquoi elle n'est pas dans la référence de l'API[​](#la-voie-anonyme-et-pourquoi-elle-nest-pas-dans-la-référence-de-lapi "Lien direct vers La voie anonyme, et pourquoi elle n'est pas dans la référence de l'API")

La voie ci-dessus a besoin d'une clé d'API, parce que c'est vous qui construisez un tableau de bord. Ce qu'appelle le navigateur d'un *visiteur* est un endpoint différent, non authentifié : le **plan wasm** de la visualisation, à

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

Il est délibérément **absent de la référence d'API générée et des deux SDK**, et cette absence est une décision de conception plutôt qu'une omission. La route est anonyme et orientée navigateur ; il n'existe aucun appel par clé d'API pour elle, donc elle n'a pas sa place dans un SDK à clé d'API. Elle est exclue au niveau du framework, ce qui explique pourquoi vous ne la trouverez pas en parcourant [la référence](/fr/reference/vibedasher-sdk-api.md) — la seule description qui en existe est cette page et le runner qui l'appelle.

Ses gardes sont **identiques** à ceux de la route de cache anonyme à côté d'elle : le compte doit exister et autoriser les tableaux de bord publics, et la visualisation doit être publique **et** publiée. Un tableau de bord privé ou protégé répond 404 ici. Rien là-dedans n'est une valeur par défaut côté client que vous pourriez surcharger.

Elle renvoie `{ sources: [...], unavailable: [...] }`. Un jeu de données qui n'est pas compatible wasm est rapporté dans **`unavailable` avec une raison** plutôt que retiré silencieusement de `sources` — parce qu'un client qui recevrait une liste `sources` courte et aucune explication afficherait un tableau de bord partiel en le déclarant terminé.

### Pourquoi un tableau de bord protégé ne pourra jamais être servi ainsi[​](#pourquoi-un-tableau-de-bord-protégé-ne-pourra-jamais-être-servi-ainsi "Lien direct vers Pourquoi un tableau de bord protégé ne pourra jamais être servi ainsi")

Les lignes d'un tableau de bord protégé sont délimitées par visionneur en réécrivant le SQL côté serveur. Un fichier Parquet présigné est **la table entière**. Il n'y a aucun moyen d'honorer un prédicat de lignes que le client pourrait simplement refuser d'exécuter — donc la voie n'est pas « pas encore implémentée » pour les tableaux de bord protégés ; elle est structurellement exclue, et elle le restera toujours.

## Ce que vous sacrifiez[​](#ce-que-vous-sacrifiez "Lien direct vers Ce que vous sacrifiez")

Énoncez-le avant de construire, parce que c'est la moitié que vous ne pourrez pas changer ensuite.

**L'extrait est indexé par jeu de données et instantané, pas par votre requête.** Le même `SELECT` avec et sans `WHERE` renvoie le fichier identique. Votre prédicat ne le réduit pas. Conséquences, toutes la même conséquence :

* Il contient **toutes les lignes** de chaque jeu de données que lit le tableau de bord.
* C'est **un seul fichier, le même fichier**, pour tous ceux qui ouvrent la page.
* La page n'émet aucune requête serveur, donc **rien dans le chemin ne pourrait donner moins de lignes à un visiteur qu'à un autre**.
* Un filtre appliqué dans le navigateur change ce qui est **dessiné**, jamais ce qui a été **livré** — et les octets sont dans l'onglet réseau dans les deux cas.
* Il sert l'**instantané publié**, pas une requête vivante. Rafraîchir ce qu'il montre veut dire republier.

En échange : aucune clé à laisser fuir, aucun jeton à renouveler, aucun backend à vous à faire tourner ou à payer, aucune limite de débit devant la page, et un tableau de bord qui continue de s'afficher depuis un hébergement statique.

**À savoir aussi : le plafond de lignes ne s'applique pas ici.** La voie côté serveur s'arrête à 100 000 lignes et positionne `truncated`, ce qui rend silencieusement faux chaque total calculé depuis le préfixe. Le Parquet de la voie wasm est le jeu de données entier par instantané, précisément parce qu'il est indexé par jeu de données plutôt que par votre résultat — donc c'est aussi la réponse honnête à un ensemble de résultats véritablement grand, pas seulement à un problème d'identifiants.

## Quand choisir autre chose[​](#quand-choisir-autre-chose "Lien direct vers Quand choisir autre chose")

| Vous avez besoin de                                          | Voie                                                                                                                       |
| ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| lignes que vous allez lire et sur lesquelles raisonner       | `run_query` / `client.query` — la voie backend par défaut                                                                  |
| beaucoup de lignes sur disque                                | `run_query_to_file(output_path=...)`                                                                                       |
| un tableau de bord public sans identifiant et sans backend   | **wasm**                                                                                                                   |
| que des visionneurs différents voient des lignes différentes | un **jeton d'intégration à portée restreinte** portant des `filters`, ou du code serveur de confiance qui restreint le SQL |
| des données vivantes plutôt qu'un instantané publié          | la voie backend, derrière un serveur que vous contrôlez                                                                    |

Un client à jeton restreint **refuse** `type="wasm"` d'emblée plutôt que de s'exécuter discrètement côté serveur : un appelant qui aurait demandé wasm et obtenu des lignes quand même n'aurait aucun moyen de s'en apercevoir. Ce refus est le même principe que `fallbackReason` — un changement de voie ne doit jamais être invisible.

Pour les deux câblages qui *peuvent* délimiter un visionneur, voir [Le filtrage au niveau des lignes](/fr/guides/who-can-see-what.md#row-level-filtering-the-embed-lane-and-only-the-embed-lane).
