Database
Zelavis App includes a project database service with documents, events, schemas, projections, and time-series. It is separate from the Platform OS System Store.
This page is the in-process API, for code running inside a Project. An App reaching its own records from outside — a desktop client, a worker, another host — uses App Data, which is the same database behind a boundary that resolves the Tenant from the caller.
Bind a Tenant first
Section titled “Bind a Tenant first”Ordinary App data access always begins with a real Tenant ID. There is no implicit default Tenant:
import { Zelavis } from 'zelavis';
const zv = new Zelavis({ adapter });const tenantDb = zv.db.forTenant('tenant_acme');
await tenantDb.documents.createCollection({ name: 'posts' });
const doc = await tenantDb.documents.insert({ collection: 'posts', data: { title: 'Hello', published: false },});
await tenantDb.documents.update({ collection: 'posts', id: doc.id, data: { published: true },});Tenant identity is not accepted inside document or event inputs. Binding it once prevents a caller from accidentally mixing data from different Tenants in one operation sequence.
API surface
Section titled “API surface”The logical database API deliberately does not expose a physical driver, SQLite filename, or raw SQL connection.
| Property | Description |
|---|---|
forTenant(tenantId) |
Returns Tenant-bound documents, events, and time-series APIs. |
schemas |
Global collection schema definitions applied across shards. |
projections |
Projection definitions and rebuild controls. |
timeseries |
Global time-series definition registry. |
capabilities |
Logical database capabilities such as Tenant routing. |
context |
Runtime configuration and Node identity. |
Low-level engine adapters still expose physical SQL on DatabaseDriver.sql
for explicitly targeted administration and adapter tests. It is not part of
DatabaseApi, because one logical App database can span several SQLite files.
Documents
Section titled “Documents”tenantDb.documents.createCollection(input)tenantDb.documents.listCollections()tenantDb.documents.collectionExists(input)tenantDb.documents.insert(input)tenantDb.documents.findById(input)tenantDb.documents.findMany(input)tenantDb.documents.update(input)tenantDb.documents.delete(input)Documents are JSON objects stored in named per-collection tables. Writes append an event before updating the materialized collection table.
Events and cursors
Section titled “Events and cursors”const page = await tenantDb.events.read({ collection: 'posts', limit: 100,});
const nextPage = await tenantDb.events.read({ collection: 'posts', after: page.at(-1)?.cursor, limit: 100,});DatabaseEvent.cursor is an opaque, versioned continuation token. Persist it
and pass it back unchanged. Do not parse, compare, increment, or assume it is a
global integer: the topology router binds cursors to the Tenant’s virtual shard.
Schemas and projections
Section titled “Schemas and projections”Schema and projection definitions live on the logical database because they must be consistent across every physical shard:
await zv.db.schemas.save({ collection: 'posts', version: 1, activate: true, fields: [ { name: 'title', field: { _tag: 'TextField', label: 'Title', required: true }, }, ],});
await zv.db.projections.register({ name: 'published-posts', source: { collections: ['posts'], eventTypes: ['document.upserted'], },});Time-series
Section titled “Time-series”Definitions are logical and Tenant-neutral; reads are Tenant-bound:
await zv.db.timeseries.define({ name: 'post-writes', source: { collections: ['posts'], eventTypes: ['document.upserted'], }, map: (event) => ({ timestamp: event.timestamp, value: 1, }),});
const handle = tenantDb.timeseries.get('post-writes');const points = await handle.range({ order: 'desc', limit: 50 });const total = await handle.aggregate({ op: 'count' });The same definition can be queried for another Tenant through a different
forTenant(...) handle without changing its mapping logic.