Vennbase

Build multi-user apps without writing a single access rule.

Vennbase is a TypeScript client-side database for collaborative, local-first web apps. It frees developers from running a backend, paying for servers, or writing finnicky access control rules. Users sign up for a Puter account to store their data. Your app sees the subset of the data shared with a user.
Write your frontend. Vennbase handles the rest.
  • User brings standardized backend — no server to run, no infrastructure bill
  • No access rules to write — share a link, they're in; that's the whole model
  • Optimistic updates — instant writes built-in
  • Local-first support — app data syncs via CRDT automatically
  • NoSQL, open source
  • Auth, server functions — via Puter, one login for your whole app
  • User-pays AI — Puter's AI APIs are billed to the user, not you; build AI features with zero hosting cost
  • Agent-friendly — the explicit-grant model is simple enough that AI coding agents get it right first time
tsx
// Write
const board = db.create("boards", { title: "Launch checklist" }).value;
db.create("cards", { text: "Ship it", done: false, createdAt: Date.now() }, { in: board });

// Read (React)
const { rows: cards = [] } = useQuery(db, "cards", {
  in: board,
  orderBy: "createdAt",
  order: "asc",
});

// Share
const { shareLink } = useShareLink(db, board, "all-editor");

For coding agents

Paste this into your coding agent so it uses Vennbase and pulls the package docs instead of inventing a backend.

Vennbase documentation

DocumentDescription
PATTERNS.mdRecipe-style app patterns for blind inboxes, index-key projections, resource claims, and other real-world Vennbase designs.

Setup

Create one Vennbase instance for your app and pass it an appBaseUrl so that share links point back to your app:

ts
import { Vennbase } from "@vennbase/core";
import { schema } from "./schema";

export const db = new Vennbase({ schema, appBaseUrl: window.location.origin });

Auth and startup

tsx
import { useSession } from "@vennbase/react";

function AppShell() {
  const session = useSession(db);

  if (session.status === "loading") {
    return <p>Checking session…</p>;
  }

  if (!session.session?.signedIn) {
    return <button onClick={() => void session.signIn()}>Log in with Puter</button>;
  }

  return <App />;
}

Creating rows

ts
// Create a top-level row
const board = db.create("boards", { title: "Launch checklist" }).value;

// Create a child row — pass the parent row or row ref
db.create("cards", { text: "Write README", done: false, createdAt: Date.now() }, { in: board });
db.create("cards", { text: "Publish to npm", done: false, createdAt: Date.now() }, { in: board });

create and update are synchronous optimistic writes. Use .value on the returned receipt when you want the row handle immediately.

To update fields on an existing row:

ts
db.update("cards", card, { done: true });

Membership

Only all-* role members can inspect the member list, and only all-editor can mutate it:

ts
// Flat list of usernames
const members = await db.listMembers(board);

// With roles
const detailed = await db.listDirectMembers(board);
// → [{ username: "alice", role: "all-editor" }, ...]

// Add or remove manually
await db.addMember(board, "bob", "all-editor").committed;
await db.removeMember(board, "eve").committed;

Membership inherited through a parent row is visible via listEffectiveMembers.

Real-time sync (CRDT)

Vennbase includes a CRDT message bridge. Connect any CRDT library to a row and all members receive each other's updates in real time.

Sending CRDT updates requires "content-editor" or "all-editor" access. Any readable role (content-* or all-*) can poll and receive them.

In React, here is the recommended Yjs integration:

tsx
import * as Y from "yjs";
import { createYjsAdapter } from "@vennbase/yjs";
import { useCrdt } from "@vennbase/react";

const adapter = createYjsAdapter(Y);
const { value: doc, flush } = useCrdt(board, adapter);

// Write to doc normally, then push immediately when needed
await flush();

@vennbase/yjs uses your app's yjs instance instead of bundling its own runtime, which avoids the multi-runtime Yjs failure mode.

Example apps

packages/todo-app is the code from this README assembled into a working app — boards, recent boards, cards, and share links. Run it with:

bash
pnpm --filter todo-app dev

For a fuller picture of how the pieces fit together in a real app, read packages/woof-app. It uses CRDT-backed live chat, user-scoped history rows for room restore, child rows with per-user metadata, and role-aware UI — the patterns you'll reach for once basic reads and writes are working.

bash
pnpm --filter woof-app dev

packages/appointment-app is the clearest example of the Vennbase access-control philosophy in a full app: explicit grants, a blind booking inbox, and minimal anonymous sibling visibility via select: "indexKeys". It demonstrates convergent client-side claim resolution, not hard capacity enforcement. Read PATTERNS.md for a recipe-style walkthrough of each pattern.

bash
pnpm --filter appointment-app dev