@voidbase-cloud/voidbase
v1.0.0
Published
PocketBase-compatible backend on Cloudflare Workers (D1, R2, Queues, Durable Objects) via Void, or a single Bun process
Maintainers
Readme
voidbase
The status page of the last green build: every step, every suite, the screenshots.
A PocketBase-wire-compatible backend on Cloudflare Workers, built with Void.
The unmodified PocketBase admin panel (0.40.2) and the unmodified pocketbase JS SDK (0.28) are the two oracles that define done.
Codename: kanz-zjy. Progress map: surface/surface.json rendered by bun run surface.
Use it like PocketBase as a framework
// main.ts
import { voidbase, parseServeArgs, type VoidbaseApp } from "@voidbase-cloud/voidbase";
export function register(app: VoidbaseApp) {
app.hooks.onRecordAfterCreateSuccess(async (e) => { /* ... */ }, "posts"); // the same on* functions pb_hooks get
app.router.get("/api/hello", (c) => c.json({ hello: "world" })); // Hono-style routes
app.hooks.cronAdd("digest", "0 8 * * *", () => { /* ... */ });
}
if (import.meta.main) { const app = await voidbase(parseServeArgs()); register(app); await app.start(); }bun main.ts --http 127.0.0.1:8090 runs it; voidbase deploy composes register into the Worker as well.
voidbase-sveltekit-starter/vb is the worked example (audit log, hooks collection actions, passkeys).
Install
bun add @voidbase-cloud/voidbase # the package: library, CLI (`voidbase`) and the Cloudflare project generator
bunx @voidbase-cloud/voidbase serve # or run the CLI without installing
voidbase local new blog # a named instance on this machine, with its own port and superuser
voidbase local ls # what you have locally; `voidbase instances` does the same for Cloudflare
voidbase update # move to the newest release, whichever way voidbase is installed
voidbase update blog # rebuild a local instance onto it in place; `voidbase rollback blog` goes back
voidbase update --cloudflare blog # the same for an instance on Cloudflare, through its own rebuild
voidbase instances create --cloudflare shop --template voidbase-demo # a cloud instance shaped like a templateOr, like PocketBase, a single prebuilt executable from the
releases: voidbase_<version>_<os>_<arch>.zip for Linux,
macOS and Windows (amd64 and arm64; musl builds for Alpine), with the admin panel, the system migrations and the hooks
typings inside, so ./voidbase serve needs nothing else. The Cloudflare toolchain (deploy, bundle, dev)
stays with the npm package.
voidbase update is the same command in every one of those shapes and works out which one it is in. The prebuilt
executable fetches the latest release for its platform, checks it against the published checksum and replaces itself
(--backup zips pb_data first). A global npm install reinstalls itself. Inside a project, the dependency is bumped
and installed, keeping the caret or the pin the project already had, and you are reminded that the live instance
changes when you deploy rather than when you install. voidbase update --check changes nothing and exits 1 when
there is a newer version and 2 when it could not find out, which is what a pipeline reads. Every other command
mentions a new release once a day; VOIDBASE_NO_UPDATE_CHECK=1 turns that off.
Commits follow Conventional Commits (enforced by husky and CI); release-please turns them into a release PR, and merging it publishes to npm (and mirrors the core to GitHub Packages) and attaches the executables, with the compiled notes; see docs/releasing.md.
Run it like PocketBase
bun install
bunx voidbase serve --http 127.0.0.1:8090 --dir pb_data --hooksDir pb_hooks --migrationsDir pb_migrations --publicDir ./publicOne Bun process, SQLite in pb_data/data.db, files in pb_data/storage/, the admin panel at /_/, the same
pb_hooks and pb_migrations you would give PocketBase (--dev restarts on hook changes, voidbase superuser
upsert email pass works offline on pb_data). --tunnel puts the instance on the internet through a Cloudflare
quick tunnel: the banner gains a Tunnel: https://<words>.trycloudflare.com line, and the tunnel closes with the
server. It runs cloudflared from VOIDBASE_CLOUDFLARED, from PATH, or downloaded once into
~/.cache/voidbase/cloudflared/; without one the server serves as usual and says so. The Cloudflare deployment
runs the same code on D1 and R2 with voidbase deploy from the same directory (see docs/deploy.md). The
PocketBase-shaped
consumer is voidbase-sveltekit-starter/vb.
Run locally (this checkout, Workers dev server)
The repository is a Bun workspace and this package is packages/voidbase; the commands below are run from the
repository root, which is where the tooling and the hoisted node_modules/.bin live.
bun install
cp packages/voidbase/.env.example packages/voidbase/.env # first superuser, upserted at bootstrap
bun run panel:sync # copies ../pocketbase/ui/dist to packages/voidbase/public/_ (POCKETBASE_UI_DIST overrides)
(cd packages/voidbase && ../../node_modules/.bin/void db migrate)
./scripts/dev.sh start 5180 # background dev server with a pidfile; stop / status / logPanel: http://127.0.0.1:5180/_/ · API: http://127.0.0.1:5180/api/health · API reference: http://127.0.0.1:5180/api/docs
Verify against PocketBase
With a reference PocketBase on 127.0.0.1:8090 (the starter's pb/ works, superuser [email protected] / changeme123):
cd packages/voidbase # the suites' relative paths are the package's
bun test/conformance/compare.ts # same requests at both servers, JSON diffed with volatile fields masked
bun test/panel-smoke.ts # headless login through the unmodified panel, screenshot to /tmp/panel.pngThe starter fork
voidbase-sveltekit-starter is the reference consumer: pocketbase-sveltekit-starter with pb/ replaced by
vb/ (a PocketBase-shaped directory: pb_hooks, pb_migrations, pb_data, main.ts, entrypoint.sh) and
nothing else changed. Its vb/package.json depends on this package; bun run backend in sk runs voidbase serve,
bun run dev:backend runs main.ts, and bun run deploy in vb goes live on Cloudflare. The original
pocketbase-sveltekit-starter checkout stays on upstream master as the PocketBase reference for the differential
suites (scripts/seed-reference.sh runs its pb/ against the reference binary).
CLI
bun bin/voidbase.ts --help (or voidbase when installed): init, dev, build, preview, deploy [--cloudflare],
superuser upsert|list, import <collections.json>, export <outDir>, panel sync [--brand dir], app sync,
seed-user. Remote commands take --url and --admin email:password.
Tests
Conformance suites in test/conformance/ run the same requests against a reference PocketBase (8090) and voidbase
(5180) and compare; browser suites test/panel-*.ts and test/starter-*.ts drive the unmodified panel and the
unmodified pocketbase-sveltekit-starter. Helpers that must be running for some suites: bun test/smtp-sink.ts
(SMTP 2525 / HTTP 2526), bun test/mock-oidc.ts (5190) and bun test/s3-mock.ts (5195, S3 with SigV4 verification). bun test/fresh-db.ts builds the production Worker
with the fixture hooks and migrations and boots it on an empty D1; bun test/mail-http.ts does the same with the HTTP mail
provider variables.
Go live
voidbase token prints a Cloudflare dashboard link that creates VOIDBASE_DEPLOY_CF_API_KEY with the right
permissions pre-selected; with that variable set, voidbase deploy provisions D1 and R2, generates the Void project
inside the package (node_modules/voidbase/.cloud/<name>), stores the superuser and what pb_secrets/ declares
as the Worker's secrets and vars, and uploads the Worker. Your directory stays pb_hooks + pb_migrations +
pb_secrets + pb_data, like a PocketBase folder: pb_secrets/main.ts declares the configuration with Void's
validators, every key wrapped in who may read it (secret, server, browser, local), the git-ignored
pb_secrets/secrets.json holds the local values, and CI
deploys with nothing but the deploy token once voidbase secrets push has stored the secrets. See
docs/deploy.md.
Continuous integration
scripts/ci.sh is the whole CI (bun run ci on a dev machine): it fetches the starter, syncs the panel, starts
voidbase and a seeded reference PocketBase (scripts/seed-reference.sh), runs every suite through
scripts/ci-suites.sh on the Workers and Bun runtimes, then the production-build boots, the prebuilt executable and
the starter smoke, and renders the status page at release.voidbase.cloud.
Cloudflare Workers Builds runs it on every push (scripts/cf-builds.ts setup); see docs/ci.md.
Docs
- docs/setup.md: start here. Connecting to an instance with the SDK or the admin panel, and the four ways to run one of your own.
- docs/adapter.md: running a Void app on voidbase, built into one Worker.
- docs/deploy.md: Void platform or your own Cloudflare account.
- docs/differences.md: what the platform changes (D1 batches, per-isolate limits, polling realtime, backups format).
- docs/hooks.md:
pb_hooksandpb_migrationson Workers, supported events and globals. - docs/migrating.md: moving an existing PocketBase app.
- docs/registry.md: the registry protocol a marketplace serves and an instance reads: three GETs, the bundle contract, integrity, and the fixture marketplace.
- docs/plugins.md: the plugin kernel: manifests, interfaces, the three plugins that exist,
/api/plugins, and what is not built. - docs/platform.md: how to run cheap and fast on Cloudflare (assets off the Worker, log writes, change feed, crons, placement, queues, rate limits, the realtime hub) and the per-app Durable Object design for going beyond the account limits.
- COMPAT.md: verified upstream versions and endpoint matrix.
Layout
routes/api/[...path].tshands every/api/*request to the Hono app insrc/server/app.ts.src/server/is the server: collections model, auth, settings, records, bootstrap.db/schema.tsdefines only the system tables. User collections are rows in_collectionsand tables created at runtime, as in PocketBase.public/_is the panel build, synced, never edited (bun run panel:sync --brand <dir>for an optional logo/title/docs-link swap).- Everything outside
/apiis served by Cloudflare's asset layer without invoking the Worker; deep links get the SPA shell through404.htmlcopies ofindex.html(written at build time byhooks-plugin.tsand by the sync scripts). crons/every-minute.tsruns PocketBase's maintenance jobs andcronAddjobs.queues/jobs.tsconsumes the jobs queue (system mail, automatic backups) with retries; without it every job runs inline.src/server/hub.tsis the realtime hub, a Durable Object exported from this Worker (hooks-plugin.tsappends it to Void's entry;wrangler.jsoncbinds it); without the binding realtime polls the D1 change feed.hooks-plugin.tsbundlespb_hooksandpb_migrationsinto the Worker at build time.
