Wisp is in pre-release. The full release will be on crates.io and GitHub, with the VS Code extension. Install the pre-release.

Wisp

Storage on Every Host

wisp::kv, wisp::blobs and wisp::sql are one API on every Wisp host. The code stays the same. One environment variable per store picks where the data goes.

let kv = wisp::kv("SESSIONS");
kv.put_for(&format!("sess:{id}"), &user, 30 * 86_400).await?;   // TTL in seconds
let who: Option<String> = kv.get(&format!("sess:{id}")).await?;
let page = kv.list_page("sess:", "", 100).await?;                 // Page { keys, cursor }

let photos = wisp::blobs("PHOTOS");
photos.put("a.png", png, "image/png").await?;
let blob = photos.get("a.png").await?;                            // Blob { body, content_type }

let rows: Vec<Recipe> = wisp::sql("DB").all("select * from recipes where owner = ?", &[&id]).await?;

The full API (metadata, absolute expiry, get_json, put_json, list) is on Bindings.

Choosing a Driver

WISP_KV_<NAME> picks the driver of wisp::kv("NAME"), WISP_BLOB_<NAME> of wisp::blobs("NAME") and WISP_SQL_<NAME> of wisp::sql("NAME"). Wisp reads and opens nothing until the first call to the store, so an app that uses none of them pays nothing. The code is not in its binary or its wasm.

WISP_KV_* / WISP_BLOB_* / WISP_SQL_*kvblobssqlRuns on
cf:BINDINGWorkers KVR2D1Cloudflare Workers and Pages
deno-kv, deno-kv:<path or url>yesyes, 64 KiB eachDeno, Deno Deploy
netlify-blobs, netlify-blobs:<store>yesyesNetlify Functions and Edge Functions
fs, fs:<dir>yesyesthe built-in server, wisp dev, containers
memoryyesyesthe built-in server (one process, gone on restart)
upstash, upstash:<url>Redis over RESTevery host
s3:<endpoint>/<bucket>yesevery host
vercel-blob, vercel-blob:privateyesevery host
libsql://<host>, https://<host>yesevery host
d1-http:<account id>/<database id>yesevery host

The drivers in the last five rows speak HTTP. On the edge they use the host's fetch. On the built-in server they use wisp::fetch. There, an https store needs Wisp's tls feature (wisp = { features = ["tls"] }, off by default). Without it, the first call answers a 500 that names the feature. wisp::fetch opens a new connection for each call, so on the built-in server each operation pays a TCP and TLS handshake (one or two round trips more than a kept connection). When that matters, keep hot keys in fs or memory.

Wisp makes the driver on a store's first call and keeps it. It reads the variables once.

Defaults per Host

HostUnset WISP_KV_* / WISP_BLOB_*Unset WISP_SQL_*
Cloudflarethe binding called NAME in wrangler.tomlthe D1 binding NAME
Deno DeployDeno KV (when there is no binding NAME)501
NetlifyNetlify Blobs, store NAME501
Vercelnone: set upstash (Vercel KV, Marketplace Redis) and vercel-blob501
Node, Bun, Azure (Node build)none: set upstash or s3:501
AWS Lambdafs under /tmp/wisp-data/storage, per instance: set upstash or s3:501
VPS, Docker, Fly, Railway, Render, Cloud Run, wisp devfs: $WISP_DATA/storage when WISP_DATA is set (memory when it is off), else .wisp/data501

A store with no driver answers a 500 on the first call. The 500 names the variable to set. It never panics.

Settings of Each Driver

  • upstash: the URL from UPSTASH_REDIS_REST_URL (or KV_REST_API_URL, the name Vercel sets) unless the spec has one. The token comes from WISP_KV_<NAME>_TOKEN, UPSTASH_REDIS_REST_TOKEN or KV_REST_API_TOKEN. Each store keeps the keys that <length of NAME>:NAME: starts, so several stores share one database and no two stores' keys meet. A put is one MULTI/EXEC (value, metadata and TTL as an EXAT). Metadata sits beside the key and no list shows it. A value that is not UTF-8 is kept as \0wisp-b64: and its base64, which other Redis clients read as such.
  • s3:<endpoint>/<bucket>: path-style, signed with AWS Signature Version 4. The key comes from WISP_BLOB_<NAME>_KEY_ID (or AWS_ACCESS_KEY_ID) and WISP_BLOB_<NAME>_SECRET (or AWS_SECRET_ACCESS_KEY). An optional WISP_BLOB_<NAME>_SESSION_TOKEN (or AWS_SESSION_TOKEN) adds a session token. The region is WISP_BLOB_<NAME>_REGION or AWS_REGION, else Wisp reads it from the endpoint: auto for R2, the region of s3.<region>.amazonaws.com, else us-east-1 (MinIO). Examples: s3:https://s3.eu-west-2.amazonaws.com/photos, s3:https://<account>.r2.cloudflarestorage.com/photos, s3:http://localhost:9000/photos.
  • vercel-blob: the token from WISP_BLOB_<NAME>_TOKEN or BLOB_READ_WRITE_TOKEN (it names the store). Keys are pathnames as given, and a put overwrites in place. A public store's CDN may serve an overwritten blob for up to 60 s. Reads of a public store go to its CDN without the token. Add :private for a private store, whose reads carry the token. VERCEL_BLOB_API_URL moves the API.
  • libsql://<host>: Turso or any libSQL server, over its HTTP pipeline. The token comes from WISP_SQL_<NAME>_TOKEN, and Wisp sends it over https only. Wisp refuses a plain http:// address with a token unless it is localhost, 127.0.0.1 or [::1]. A BLOB column reads as its base64 text.
  • d1-http:<account>/<database>: D1 from outside Cloudflare, over its REST API. The token (with D1 edit) comes from WISP_SQL_<NAME>_TOKEN or CLOUDFLARE_API_TOKEN. Wisp writes true and false arguments as 1 and 0, as the binding and libSQL store them.
  • deno-kv: the keys are [kind, NAME, key] in the database Deno.openKv opens (the same handle as WISP_STORE=deno-kv). Local Deno needs --unstable-kv. Deno Deploy has it.
  • netlify-blobs: the site store NAME (or the one the spec names), reached with the NETLIFY_BLOBS_CONTEXT that Netlify gives each function. It needs no npm package.
  • fs:<dir>: each key is one file under <dir>/<kv or blob>/<name>/. Wisp escapes the file name so no key leaves the folder or clashes on a case-blind disk. A put writes to a temporary file, flushes to disk and renames, so a reader never sees half a value. File calls run on blocking threads, never the request's worker.

What Each Driver Keeps

  • Expiry (put_for, KvPut { ttl, expires }) holds on every driver. Cloudflare needs at least 60 s. Deno KV and Netlify Blobs drop expired keys when read.
  • Lists come in key order. upstash pages follow Redis's SCAN. Each page is sorted, and a page may hold fewer keys than asked. upstash, netlify-blobs, s3: and vercel-blob list names only, with no expiry or metadata. Netlify Blobs takes no page size.
  • An s3: or vercel-blob key that is empty or has a . or .. segment (../x, a/./b) gets a 400. Many servers and proxies (MinIO, R2) resolve such a path to another object, and an empty one names the bucket's listing. A list's prefix may be empty.
  • A Deno KV value is at most 64 KiB (65,536 bytes, with its metadata and type): a larger put answers 413 and keeps nothing. Keep larger blobs in s3: or vercel-blob.

Host Bindings Stay on Their Host

wisp::platform::call(binding, method, ...) reaches a Cloudflare binding (Workers AI, Queues, Durable Objects) and runs only there. Anywhere else it answers 501, because the binding exists nowhere else. Use the binding's HTTP API through wisp::fetch when the same code must run off Cloudflare.

Is This Page Useful?

View This Page's Source

Type to search every docs page and heading.