Browse the docs
Examples
Signature example
Share an agreement or PDF and collect drawn signatures tied to verified identities. Storage, summary counters, and email.
bunx @revnu/fuast new nda --template signatureShare an agreement or PDF with your team and collect signatures. Each signature is tied to the signer's verified fuast account.
What it shows
auth: { requireSignIn: true }: no guests, because a signature needs a real identity.documentsandsignaturestables linked bydocId: id("documents"), with aby_doc_signerindex that enforces one signature per person.- Attachments and signature images uploaded with
client.storage.upload. Rows store only the storage key, and thedocumentquery turns keys into URLs withctx.storage.url(). - Client-sent keys checked with
ctx.storage.stat()before a row points at them: the object must exist and the caller must have uploaded it, so nobody can attach (and later delete) someone else's file. - A
signatureCountfield updated in the same transaction as each signature, so the list never counts rows. - An email to the author for every new signature, sent as the last step of the mutation.
deleteDocumentremoves rows first and storage objects last, because storage is outside the transaction.- Client routes
/and/docs/:id, and a dependency-free canvas signature pad.
Upload, then write
const uploaded = await client.storage.upload(png); // private by default
await sign(docId, uploaded.key);async function uploadedBy(ctx: { storage: StorageContext }, key: string, userId: string) {
if (!isStorageKey(key)) return false;
const info = await ctx.storage.stat(key); // null when the object does not exist
return info !== null && info.uploadedBy === userId;
}
sign: mutation(async (ctx, docId: string, imageKey: string) => {
const me = ctx.auth.requireSignedIn();
if (!(await uploadedBy(ctx, imageKey, me.userId))) throw new Error("Draw your signature again.");
const doc = await ctx.db.documents.get(docId);
if (!doc) throw new Error("This document no longer exists.");
const existing = await ctx.db.signatures
.withIndex("by_doc_signer", (q) => q.eq("docId", docId).eq("signerId", me.userId))
.first();
if (existing) throw new Error("You already signed this document.");
await ctx.db.signatures.insert({ docId, signerId: me.userId, name: me.displayName, imageKey });
await ctx.db.documents.update(docId, { signatureCount: doc.signatureCount + 1 });
// email the author last
})The signer's name and email come from ctx.auth, so nobody can sign as someone else. createDocument checks the attachment key the same way.
Try it
bunx @revnu/fuast devCreate a document as dev, then open it as ?fuast_user=alice and sign. The author's email appears in bunx @revnu/fuast logs --port 3000. Deploy and share with your workspace:
bunx @revnu/fuast deploy
bunx @revnu/fuast share --visibility workspaceAdd an outside signer with --emails them@partner.com.