Data, Files and Jobs
Everything here is opt-in: an app that uses none of it pays nothing.
Tables
// src/db.rs
pub static USERS: Table<User> = Table::saved("users")
.unique("email", |u: &User| &u.email) // no two rows share it
.migrate(|row| rename(row, "mail", "email")) // old rows, as they are read
.live(); // each change is sent on channel "users"
set(id, v) -> Option<()>replaces a row;clear()takes every row out (ids are not given again).unique:by(&email)finds the row without a scan.try_add,try_setandtry_updatereturn a 422 on the field (email: is taken), for an action to return with?.add,setandupdatepanic (a 500) instead. Usetry_updateto change a unique field.- On a
#[model]field:#[unique]is the same as.unique(..);#[json(default)]or#[json(default = expr)]fills a field old rows lack;#[json(was = "old")]reads it under its old name. migrate(|v: &mut Value| ..)runs on each stored row's JSON when it is read (at load, and byWISP_STORE_POLL), before it becomes the type.live(): after each change,wisp::channel(name).send("change"), only when someone listens. A page listens withlisten('/users/events', invalidate)and a+server.rsofwisp::channel("users").events(). A page that names a live table's static rows refreshes itself through/_wisp/live/<name>.- Several servers on one store: a
Storemay answerchanges(table, since), and withWISP_STORE_POLL=5(seconds) every table follows it. - Tables are whole in memory: the store's size is the RAM bound.
Rules
#[validate(url, one_of = "a b c", pattern = "[a-z]{3}-\\d+", with = ok)] beside len min max min_len max_len email.
urlis an absolute http(s) address.patternmatches the whole value: literals,.,[a-z_],[^x],\d \w \s, groups,|,? * + {n} {n,m}(values up to 1000 characters; too much backtracking does not match).with = fcallsf(&v) -> Option<String>, the problem.
Jobs
// init
wisp::work("mail", |m: Mail| async move { send(&m).await });
wisp::cron("0 3 * * *", || async { purge().await }); // UTC
// anywhere
wisp::queue("mail").push(&Mail { to, body }); // .later(secs, &job)
- Jobs live in the saved table
queue-mail. A failure (Error a panic) is tried again after 4, 8, 16... seconds (an hour at most), five tries; then it staysdeadwith its last error (queue.dead(),queue.retry(id)). - At least once: a job running when the process died runs again a minute later. One at a time per queue, in order.
crontakes five fields (*,n,a-b,*/n, lists; Sunday is 0 or 7).- On Cloudflare, Vercel and Netlify (
wisp build --target ...) the same code runs from the host's cron:wisp buildwrites eachcronschedule (a string literal) into its trigger config, and a trigger also runs the queues' due jobs (each minute if the app haswork). SetCRON_SECRETandWISP_STOREthere. See /docs/deploy.
Cache
wisp::cache("top", 60, || async { top_posts().await }).await keeps an answer for 60 s per process. wisp::uncache("/posts") forgets cache keys starting with it and the pages const CACHE keeps, on every worker, for /posts and below (/ is all).
Is This Page Useful?