Building with Baseflare requires three mental models: the document model (how data is stored and validated), the function model (how server logic is organised into queries, mutations, and actions), and the permission model (how access is controlled). Each model is designed to be simple in isolation and composable together. This page walks through all three, plus the Cloudflare resource mapping and subpath import structure that underpin the whole system.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/nickruigrok/baseflare/llms.txt
Use this file to discover all available pages before exploring further.
Document Model
Baseflare stores every collection as a D1 (SQLite) table with a small, fixed set of columns managed by the framework. Field definitions indefineTable describe the shape of the _data JSON blob — they are enforced at write time by the Worker runtime, not at the database level. This means adding, removing, or renaming a field in your schema never requires a database migration. Old documents continue to return fields that have since been removed from the schema until those documents are rewritten.
The raw table structure created by defineSchema looks like this:
_id— A plain UUIDv7 string. Time-sortable, globally unique, and universally parseable without any custom encoding._data— The full document stored as a JSON string:{"text":"hello","completed":false,"ownerId":"user_123"}._rev— An internal row revision counter used by the runtime’s optimistic concurrency control. Never exposed through the document API.
.index("name", ["field1", "field2"]) become SQLite expression indexes on json_extract() calls, created by the deploy step before the Worker receives traffic:
.withIndex() like in Convex. Define your indexes in the schema and write .filter() calls; the database does the rest.
Function Types
Baseflare organises server logic into three function types, each with a distinct responsibility and a matching context object.| query | mutation | action | |
|---|---|---|---|
Has ctx.db | ✅ Read-only | ✅ Read + write | ❌ No direct DB access |
| Can write | ❌ No | ✅ Yes | Via ctx.runMutation() only |
| Auto-retried | — | ✅ Yes (OCC, if safe) | ❌ No |
| Use case | Fetch and return data | Atomic database writes | Side effects, external APIs, webhooks |
QueryCtx with a read-only ctx.db and ctx.auth. They must not produce side effects.
Mutations are the atomic write primitive. The runtime tracks reads and writes, detects conflicts with optimistic concurrency control, and retries the handler when it can do so safely. Mutation handlers must be deterministic and retry-safe — side-effectful work belongs in actions.
Actions handle side effects: calling external APIs, sending email, processing webhooks, charging payments. They do not have direct ctx.db access. Use ctx.runQuery() and ctx.runMutation() for database work from within an action. Each ctx.runMutation() call is its own transaction, so atomic multi-write workflows should be consolidated into a single mutation.
internalQuery, internalMutation, and internalAction — which can only be called from within the server (via ctx.runQuery, ctx.runMutation, ctx.runAction) and are never exposed through the public RPC API.
UUIDv7 IDs
All Baseflare document IDs are plain UUIDv7 strings — time-sortable, globally unique, and universally parseable without any custom encoding or decoding layer. There is no table-name encoding baked into the ID; everyctx.db call takes an explicit table parameter.
The _createdAt timestamp is derived from the ID at read time. UUIDv7’s first 48 bits encode milliseconds since the Unix epoch, so no separate column is needed. The deserialize() function computes and injects _createdAt automatically on every document read.
The baseflare/values subpath exports two ID utilities you can use directly:
_id is equivalent to ordering by insertion time — a useful property for feeds and paginated lists.
Permissions
Baseflare’s permission system is deny-by-default. If a table or operation has no rule defined, access is denied entirely — there is no fallback to “allow all”. This makes the secure baseline the zero-configuration baseline. Rules are declared per-table withdefineRules from baseflare/server. Each table can define up to four operations:
| Operation | When it runs | Context provided |
|---|---|---|
read | Before a document is returned to the caller | ctx, doc (the candidate document) |
insert | Before a new document is written | ctx, value (the document being inserted) |
update | Before an existing document is patched or replaced | ctx, existingDoc (the current document) |
delete | Before a document is deleted | ctx, existingDoc (the document to be deleted) |
async function returning boolean. Returning false surfaces a PERMISSION_DENIED RPC error to the caller. Throwing an error from a rule propagates as an INTERNAL_ERROR.
ctx.db.query().collect(), ctx.db.get(), ctx.db.insert(), ctx.db.patch(), ctx.db.replace(), and ctx.db.delete(). Read rules filter results silently — documents that fail the read rule are excluded from query results rather than causing an error.
Cloudflare Resource Mapping
Every Baseflare environment maps directly to a set of Cloudflare primitives on your account. There is no hosted control plane or intermediary layer between your Worker and the platform.D1 — Database
One D1 SQLite database per environment. Stores all collection tables using
the document model (
_id, _data, _rev). Includes built-in 30-day Time
Travel for point-in-time recovery.R2 — File Storage
One R2 bucket per environment. Zero egress fees, S3-compatible. Used by
ctx.storage for signed upload URLs and server-side file management.
Planned for a future phase.Durable Objects — Real-time & Scheduling
RealtimeConnectionDO holds WebSocket connections via the Hibernation API.
RealtimeSubscriptionDO tracks query subscriptions and fans out updates.
SchedulerDO manages delayed and scheduled function execution. Planned
for future phases.Vectorize — Vector Search
One Vectorize index per environment when
v.vector() fields are present in
the schema. Vectors are stored separately from _data JSON and queried
via .vectorSearch() on the query builder. Planned for a future phase.Workers — Compute
One Worker script per environment. Deployed via the Cloudflare Workers API.
Handles all RPC routing (
/api/query/*, /api/mutation/*,
/api/action/*), custom HTTP endpoints, WebSocket upgrades, and scheduled
events.Worker Secrets — Config
Cloudflare-managed encrypted secrets bound to the environment Worker.
Accessed via
process.env in Worker code. Managed via the CLI or the
Cloudflare dashboard.Subpath Imports
Thebaseflare npm package exposes independent subpath exports so that importing one surface does not pull in the runtime code of another. Each subpath is a distinct entry point with its own types and import conditions in package.json.
| Import | Purpose | Status |
|---|---|---|
baseflare/values | Validators (v.*), typed errors (BaseflareError), RPC shapes, pagination types, and ID utilities (generateId, getCreatedAtFromId) | ✅ Implemented |
baseflare/server | Schema (defineSchema, defineTable), function wrappers (query, mutation, action), permissions (defineRules), HTTP router, query builder, serialization, and Worker factory (createWorker) | ✅ Implemented |
baseflare/client | BaseflareClient class, WebSocket connection manager, subscription state, optimistic updates, and auth methods | 🔜 Planned |
@baseflare/react | BaseflareProvider, useQuery, useMutation, useAction, usePaginatedQuery, useAuth, and SSR preloading helpers | 🔜 Planned |
baseflare/values is the shared leaf — it has no dependency on baseflare/server or baseflare/client and can be safely imported in both Worker and browser code.