GitDB is a serverless RDB-like database backed by a GitHub repository. Data is stored as committed files in that repository: a manifest, replayable mutation logs, and optional plaintext snapshots. There is no database server to run.
It is built for extensions, static apps, demos, agents, and small tools that need relational database behavior without owning backend infrastructure.
serverless app
-> GitDB table, SQL, transaction, and index operations
-> GitHub repository as the database
-> Git commits as durable history
GitDB gives serverless code a small RDB shape: schema, rows, indexed selects, joins, deletes, transactions, plaintext or encrypted storage, and Git history. GitHub latency and rate limits are part of the database tradeoff; GitDB is for low-frequency application data, not hot OLTP.
Use GitDB when you want:
- A GitHub repository per project or app, for example
my-app-db - No backend database server for an extension, static app, demo, agent, or tool
- A small RDB-like API over GitHub storage: tables, SQL, transactions, indexes
- Plaintext mode where repository files can be inspected and reviewed
- Encrypted mode where manifest and mutation logs are opaque in GitHub
- Durable history that is just Git history
Do not use GitDB for high-throughput OLTP, low-latency multi-writer workloads, large analytics, secret management, or data that should not be reachable by a client-side GitHub token.
| Area | Current behavior |
|---|---|
| Serverless package export | @3xhaust/gitdb/browser with fetch-based GitHub stores and Web Crypto |
| Node entrypoint | @3xhaust/gitdb with CLI, local stores, Octokit-backed GitHub stores |
| App API | GitDb.open, openGitDb, defineTable, typed table handles |
| Table API | insert, insertMany, upsert, upsertMany, select, first, deleteWhere |
| SQL engine | CREATE TABLE, INSERT, DELETE, SELECT, joins, grouping, ordering, aggregates |
| GitHub storage | Plaintext and encrypted manifest/log stores using non-force Git ref updates |
| Local storage | Plaintext/encrypted stores for Node development and CLI use |
| Durability | Manifest-gated mutation log replay |
| Concurrency | Single writer per opened database; GitHub writes retry on branch conflicts |
| CLI | gitdb keygen, gitdb query, gitdb check |
Install:
npm install @3xhaust/gitdbUse the browser-safe package export when the app has no server:
import { defineTable, GitDb, GitHubFetchPlaintextStore } from "@3xhaust/gitdb/browser"
import { z } from "zod"
const TodoRow = z.object({
id: z.string(),
title: z.string(),
done: z.boolean(),
})
const Todo = defineTable({
columns: { done: "BOOLEAN", id: "STRING", title: "STRING" },
indexes: [{ columns: ["done"], name: "todos_done_idx" }],
name: "todos",
primaryKey: "id",
row: TodoRow,
})
const db = await GitDb.open({
store: new GitHubFetchPlaintextStore({
branch: "main",
owner: "your-github-user",
prefix: "gitdb/v1",
repo: "my-extension-db",
token: userProvidedGithubToken,
}),
syncSchema: true,
tables: [Todo],
})
const todos = db.table(Todo)
await todos.upsert({ done: false, id: "t1", title: "Ship extension sync" })
const openTodos = await todos.select({ done: false })Encrypted serverless mode:
import {
createWebAesGcmCipher,
GitDb,
GitHubFetchEncryptedStore,
} from "@3xhaust/gitdb/browser"
const cipher = await createWebAesGcmCipher(base64UrlEncoded32ByteKey)
const db = await GitDb.open({
store: new GitHubFetchEncryptedStore(
{
branch: "main",
owner: "your-github-user",
prefix: "gitdb/v1",
repo: "my-private-db",
token: userProvidedGithubToken,
},
cipher,
),
})For extensions, include GitHub API host permissions and use a user-scoped token or OAuth flow with repository contents access. Do not hard-code a broad personal token into a published extension.
Plaintext mode writes reviewable JSON:
gitdb/v1/
manifest.json
log/
00000000000000000001.json
00000000000000000002.json
Node plaintext stores can also write page snapshots:
gitdb/v1/
snapshot.json
todos/
schema.json
pages.json
pages/
000000.json
indexes.json
Encrypted mode writes opaque files:
gitdb/v1/
manifest.enc
log/
00000000000000000001.enc
The manifest is the committed boundary. Only manifest-listed log segments are replayed, so orphan files do not become database state.
The root package export includes Node-only local stores and CLI helpers:
import { defineTable, GitDb, LocalPlaintextStore } from "@3xhaust/gitdb"
const db = await GitDb.open({
store: new LocalPlaintextStore({ root: ".gitdb" }),
})Generate an encryption key:
gitdb keygenCheck a configured store:
GITDB_ENCRYPTION=off GITDB_ROOT=.gitdb gitdb checkExecute one SQL statement:
GITDB_ENCRYPTION=off GITDB_ROOT=.gitdb \
gitdb query "CREATE TABLE todos (id STRING, title STRING)"GitHub-backed stores use the same environment shape as the examples:
GITDB_GITHUB_OWNER=3x-haust
GITDB_GITHUB_REPO=my-project-db
GITDB_GITHUB_BRANCH=main
GITDB_GITHUB_PREFIX=gitdb/v1
GITDB_GITHUB_TOKEN=github_token_with_contents_write_accessThe repository keeps exactly two live GitHub-backed examples. Both configure the browser-compatible GitHub store adapter, then use only the GitDB table API so the same shape applies to extensions and static apps:
examples/api-plaintextexamples/api-encrypted
Run them against a separate database repository:
corepack pnpm exampleSet GITDB_GITHUB_OWNER, GITDB_GITHUB_TOKEN, and GITDB_KEY first.
GITDB_GITHUB_REPO defaults to gitdb-example-db. The examples create the repo
when it is missing, write real Git commits, then read the data back from GitHub.
corepack pnpm benchmark
GITDB_BENCH_ROWS=250 corepack pnpm benchmark:compare
GITDB_BENCH_ROWS=250 GITDB_BENCH_GITHUB_ROWS=25 corepack pnpm benchmark:compare:githubBenchmarks show operation timings for local and live GitHub stores: insert, filtered select, join, and filtered delete over real rows. The GitHub profile writes plaintext and encrypted data to an actual repository, so those rows include API latency and Git commit time.
corepack pnpm check
corepack pnpm test
corepack pnpm build
corepack pnpm benchmark
corepack pnpm benchmark:evaluate
corepack pnpm pack:dry-run
corepack pnpm publish:dry-run
corepack pnpm exampleMIT