VoidDB ORM

Official TypeScript ORM for VoidDB with short `vdb` commands, `.schema` files, generated declarations in `.voiddb/generated`, schema pull and push, migrations, and a typed query builder that can emit raw JSON.

Short CLI

`vdb init`, `vdb pull`, `vdb push`, `vdb gen`, `vdb dev`, `vdb status`, and `vdb deploy` are built in.

Structured layout

Projects get `.voiddb/config.json`, `.voiddb/schema/app.schema`, `.voiddb/generated`, and `.voiddb/migrations`.

Generated types

Generated `.d.ts` files and folder-level re-exports keep model types easy to import.

JSON-friendly queries

Use the fluent builder for typed filtering, then call `.json()` when you need the raw query payload.

Install

npm install @voiddb/orm
bun add @voiddb/orm

Initialize a project

The CLI reads `.env`, `.env.local`, `.voiddb/.env`, and `.voiddb/config.json` automatically.

npx --package=@voiddb/orm vdb init
.voiddb/
  config.json
  schema/
    app.schema
  generated/
    voiddb.generated.d.ts
    index.d.ts
    index.js
  migrations/

Environment-first client

import { VoidClient, query } from "@voiddb/orm";

const client = VoidClient.fromEnv();
await client.login(
  process.env.VOIDDB_USERNAME!,
  process.env.VOIDDB_PASSWORD!
);

const users = client.db("app").collection("users");

const rows = await users.find(
  query()
    .where("active", "eq", true)
    .orderBy("createdAt", "desc")
    .limit(20)
);

const plainRows = rows.toArray();
const jsonRows = rows.json();
const raw = query()
  .where("age", "gte", 18)
  .orderBy("name")
  .limit(10)
  .json();

console.log(raw);

Direct equality shorthand works too: `find({ where: { isAdmin: false } })`. If you prefer, `.query(...)` is an alias for `.find(...)`.

The new `.schema` format

datasource db {
  provider = "voiddb"
  url      = env("VOIDDB_URL")
}

generator client {
  provider = "voiddb-client-js"
  output   = "../generated"
}

database {
  name = "app"

  model User {
    id String @id
    email String @unique
    name String
    createdAt DateTime @default(now())
    updatedAt DateTime @default(now()) @updatedAt
    @@map("users")
  }
}

Older top-level `model ...` syntax is still supported for compatibility.

Short commands

npx --package=@voiddb/orm vdb pull
npx --package=@voiddb/orm vdb push
npx --package=@voiddb/orm vdb gen
npx --package=@voiddb/orm vdb dev --name add_users
npx --package=@voiddb/orm vdb status
npx --package=@voiddb/orm vdb deploy
bunx --package @voiddb/orm vdb init
bunx --package @voiddb/orm vdb pull
bunx --package @voiddb/orm vdb dev --name add_users

After local install you can use `npx vdb ...` or `bunx vdb ...` directly.

Pull, push, and migrate

VOIDDB_URL=https://db.lowkey.su
VOIDDB_USERNAME=admin
VOIDDB_PASSWORD=your-password
npx vdb pull
npx vdb push
npx vdb dev --name add_status
npx vdb status

Type generation runs automatically after `pull`, `push`, `dev`, and `deploy` unless `--no-generate` is passed. Schema sync only touches databases explicitly present in the schema file.

Generated type imports

import type {
  User,
  VoidDbGeneratedCollections,
  VoidDbGeneratedCollectionsByPath,
  VoidDbGeneratedDatabases,
} from "./.voiddb/generated";

type ExactLowkeyUser = VoidDbGeneratedDatabases["lowkey"]["users"];
type AnyUsersCollection = VoidDbGeneratedCollections["users"];
type ExactPath = VoidDbGeneratedCollectionsByPath["lowkey/users"];

Relation includes and cache

const result = await client
  .db("app")
  .collection("users")
  .findWithRelations<{
    profile: { _id: string; bio: string };
  }>(
    query()
      .where("_id", "eq", "user-1")
      .include({
        as: "profile",
        relation: "many_to_one",
        target_col: "profiles",
        local_key: "profile_id",
        foreign_key: "_id",
      })
  );
await client.cache.set("session:alice", { ok: true }, 3600);
const session = await client.cache.get("session:alice");
await client.cache.delete("session:alice");

Blob fields and uploads

const assets = client
  .db("media")
  .collection("assets");

const ref = await assets.uploadFile(
  "asset-123",
  "original",
  new TextEncoder().encode("hello"),
  {
    filename: "hello.txt",
    contentType: "text/plain",
  }
);

console.log(ref._blob_url);
console.log(assets.blobUrl(ref));
await assets.deleteFile("asset-123", "original");

// A Blob field resolves to:
{
  "_blob_bucket": "media",
  "_blob_key": "assets/asset-123/original/hello.txt",
  "_blob_url": "https://db.example.com/s3/media/assets/asset-123/original/hello.txt"
}