La corsia di query wasm
La maggior parte delle query gira dalla nostra parte: tu invii l'SQL, noi lo autorizziamo, lo eseguiamo e restituiamo righe tipizzate. La corsia wasm ribalta questo. Chiedi un piano invece di righe — Parquet prefirmato, un file per dataset, più l'SQL da eseguire su di esso — e la query viene eseguita nel browser del visitatore attraverso DuckDB-WASM.
È la corsia dietro una dashboard che non ha bisogno di alcuna credenziale: nessuna chiave API, nessun token di incorporazione, nessun server tuo. È anche la corsia con il compromesso più netto di tutta la piattaforma, e il compromesso non è nascosto da nessuna parte — è lo stesso fatto di "nessun segreto da far trapelare", letto dall'altro lato.
Come chiederla
Un argomento nella chiamata di query seleziona la corsia.
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)
Su MCP è la stessa scelta, scritta run_query(sql, type='wasm').
type è "backend" (il default) o "wasm". Tutto ciò che sta sopra la scelta della corsia è
invariato: autorizzazione, validazione dei soli alias e iniezione dei filtri avvengono ancora
tutte lato server, prima del controllo di capacità. Chiedere wasm non salta alcun controllo.
Un dataset che la chiave non può raggiungere viene rifiutato prima che qualsiasi estratto sia
materializzato o qualsiasi URL firmato.
Ramifica su mode, mai su ciò che hai chiesto
Chiedere wasm non garantisce di ottenerlo. Se qualunque dataset coinvolto non è abilitato a wasm, la query viene eseguita lato server esattamente come di consueto — e lo dichiara invece di fallire.
| Campo | Significato |
|---|---|
mode | Cosa è effettivamente girato: "backend" o "wasm". È questo il campo su cui ramificare. |
fallbackReason | Impostato solo quando type="wasm" è stato chiesto e rifiutato, e nomina il perché. |
plan | Impostato solo quando mode == "wasm". |
columns / rows | Vuoti quando mode == "wasm" — non è stato calcolato nulla. |
| La tabella riporta la grafia del protocollo. L'SDK Python le espone in snake_case | |
(result.fallback_reason), il client TypeScript in camelCase, e i parametri degli strumenti MCP | |
sono snake_case in ogni caso — quindi type resta type ovunque, ma fallbackReason in Python | |
si scrive fallback_reason. |
Un fallback silenzioso sarebbe qui il comportamento peggiore possibile: il tuo codice riceverebbe righe, crederebbe di essere sulla corsia senza credenziali, e spedirebbe una build che ha bisogno di una credenziale che non ha. Quindi il fallback non è mai silenzioso, e la ragione è sempre nominata.
Cosa contiene un piano
{
"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è l'SQL iniettato — filtri applicati, parametri già letterali con escape. Non è il tuo SQL grezzo, ed è questo il punto: entrambe le corsie calcolano risultati identici, così che una dashboard non possa significare una cosa servita dal nostro motore e un'altra cosa servita dal browser.sources[].aliasè ilcleanSQLNamedel dataset — esattamente il nome della tabella a cuiplan.sqlfa riferimento, e il nome sotto cui il file Parquet deve essere registrato in locale. Il file porta già nomi di colonna puliti.sources[].urlè prefirmato e in sola lettura. È scaricabile da qualsiasi origine, e così è anche l'endpoint del piano stesso.
Eseguire un piano è: scaricare ogni url, registrarla in DuckDB-WASM come vista chiamata
alias, eseguire plan.sql. Una dashboard espulsa su questa corsia spedisce un runner che fa
esattamente questo e nient'altro; la sua unica dipendenza è @duckdb/duckdb-wasm, che è su npm.
Devi chiedere l'estratto prima che esista
Questa è l'unica regola di sequenza, e saltarla produce un ciclo che non termina mai.
Chiedere type="wasm" è la sola cosa che costruisce l'estratto wasm di un dataset. L'estratto
è materializzato in modo lazy, da quella richiesta — o dal percorso di aggiornamento dopo che un
dataset diventa pronto — e da nessun'altra parte.
Quindi se l'espulsione riporta dataLane.id == "token" con la
ragione no-wasm-extract, la soluzione è eseguire una query per ogni dataset nominato con
type="wasm" — va bene qualsiasi SELECT banale sull'alias — e poi espellere di nuovo. Un piano
restituito significa che l'estratto ora esiste; per questo scopo puoi ignorare completamente il
contenuto del piano.
Pubblicare non cuoce un estratto. "Ripubblica e riespelli" girerà all'infinito.
La corsia anonima, e perché non è nel riferimento API
La corsia sopra ha bisogno di una chiave API, perché sei tu che costruisci una dashboard. Quello che chiama il browser di un visitatore è un endpoint diverso e non autenticato: il piano wasm della viz, a
GET /v1/public/viz/{account_slug}/{viz_slug}/wasm-plan
È deliberatamente assente dal riferimento API generato e da entrambi gli SDK, e quell'assenza è una decisione di progetto piuttosto che un'omissione. La route è anonima e rivolta al browser; non c'è alcuna chiamata con chiave API per essa, quindi non ha posto in un SDK a chiave API. È esclusa a livello di framework, motivo per cui non la troverai sfogliando il riferimento — la sola descrizione che esiste è questa pagina e il runner che la chiama.
Le sue protezioni sono identiche a quelle della route di cache anonima che le sta accanto: l'account deve esistere e permettere dashboard pubbliche, e la viz deve essere pubblica e pubblicata. Una dashboard privata o protetta risponde 404 qui. Nulla di tutto ciò è un default lato client che puoi sovrascrivere.
Restituisce { sources: [...], unavailable: [...] }. Un dataset non abilitato a wasm è riportato
in unavailable con una ragione invece di essere tolto da sources in silenzio — perché un
client che ricevesse un elenco sources corto e nessuna spiegazione renderebbe una dashboard
parziale e la considererebbe finita.
Perché una dashboard protetta non potrà mai essere servita così
Le righe di una dashboard protetta sono delimitate per visualizzatore riscrivendo l'SQL lato server. Un file Parquet prefirmato è l'intera tabella. Non c'è modo di onorare un predicato di riga che il client potrebbe semplicemente rifiutare di eseguire — quindi per le dashboard protette la corsia non è "non ancora implementata"; è strutturalmente esclusa, e lo sarà sempre.
A cosa stai rinunciando
Dichiaralo prima di costruire, perché è la metà che non puoi cambiare dopo.
L'estratto ha come chiave il dataset e lo snapshot, non la tua query. Lo stesso SELECT con
e senza WHERE restituisce lo stesso file identico. Il tuo predicato non lo rimpicciolisce.
Conseguenze, tutte la stessa conseguenza:
- Contiene tutte le righe di ogni dataset che la dashboard legge.
- È un unico file, lo stesso file, per tutti quelli che aprono la pagina.
- La pagina non emette alcuna query al server, quindi nulla nel percorso potrebbe dare a un visualizzatore meno righe che a un altro.
- Un filtro applicato nel browser cambia ciò che viene disegnato, mai ciò che è stato consegnato — e i byte sono nel tab di rete in ogni caso.
- Serve lo snapshot pubblicato, non una query dal vivo. Aggiornare ciò che mostra significa ripubblicare.
In cambio: nessuna chiave da far trapelare, nessun token da rinnovare, nessun backend tuo da far girare o pagare, nessun rate limit davanti alla pagina, e una dashboard che continua a renderizzare da un host statico.
Vale anche la pena sapere: il limite sulle righe non si applica qui. La corsia lato server si
ferma a 100.000 righe e imposta truncated, che rende silenziosamente sbagliato ogni totale
calcolato sul prefisso. Il Parquet della corsia wasm è l'intero dataset per snapshot, proprio
perché ha come chiave il dataset invece del tuo risultato — quindi è anche la risposta onesta a un
insieme di risultati davvero grande, non solo a un problema di credenziali.
Quando scegliere qualcos'altro
| Ti serve | Corsia |
|---|---|
| righe che leggerai e su cui ragionerai | run_query / client.query — la corsia backend di default |
| molte righe su disco | run_query_to_file(output_path=...) |
| una dashboard pubblica senza credenziali e senza backend | wasm |
| che visualizzatori diversi vedano righe diverse | un token di incorporazione con scope che porta filters, o codice server fidato che restringe l'SQL |
| dati dal vivo invece di uno snapshot pubblicato | la corsia backend, dietro un server che controlli tu |
Un client con token con scope rifiuta type="wasm" senza mezzi termini invece di girare in
silenzio lato server: un chiamante che ha chiesto wasm e ha ottenuto righe comunque non avrebbe
modo di accorgersene. Quel rifiuto segue lo stesso principio di fallbackReason — un cambio di
corsia non deve mai essere invisibile.
Per le due configurazioni che possono restringere un visualizzatore, vedi Filtraggio a livello di riga.