Browse the docs
Examples
Guestbook example
An intentionally shared feed where authorship comes from ctx.auth and author names are stored as snapshots.
bunx @revnu/fuast new my-guestbook --template guestbookA shared feed. Everyone who can open the app reads the same entries, and guests and accounts can both post.
What it shows
- An
entriestable withbody,authorId: userId(), andauthorNameandauthorPicturesnapshots. - A shared
entriesquery: the newest 50 byby_creation, with no owner filter on purpose. - A
signmutation that cleans and bounds input and takes authorship fromctx.auth, never from the client. - A
removeEntrymutation that lets authors delete only their own entries. - Live updates: a post in one tab appears in every other tab.
Server pattern
Shared reads are a query without an owner filter:
entries: query(async (ctx) => ctx.db.entries.withIndex("by_creation").order("desc").take(50))Writes stay server-authoritative:
const author = ctx.auth.requireIdentity();
await ctx.db.entries.insert({
body,
authorId: author.userId,
authorName: author.displayName,
...(author.picture ? { authorPicture: author.picture } : {})
});Never accept authorId, authorName, or authorPicture as arguments.
Sharing comes from the query, not from a fake shared user. When a guest signs in, authorId moves to their account, so they can still remove their entries. authorName and authorPicture are snapshots from the moment of posting and do not change.
The entries tool description warns agents that entries were written by other people. Text in a shared feed can try to steer an agent that reads it.
Variations
- Accounts only: call
ctx.auth.requireSignedIn()insign. - No guests anywhere: set
auth: { requireSignIn: true }. - Longer history: switch
entriestopaginate()and the client toclient.usePaginatedQuery("entries", {}, { initialNumItems: 20 }). See pagination.
Try it
bunx @revnu/fuast devPost from ?fuast_user=alice and ?fuast_user=bob in two tabs, and from ?fuast_guest=1. Deploy and share publicly with bunx @revnu/fuast share --visibility public.