@gomagentic/verdict-store
v0.1.1
Published
Verdict storage abstraction and adapters: in-memory, SQL (SQLite/D1/Postgres via minimal drivers), and object storage (filesystem, R2/S3-compatible).
Maintainers
Readme
@gomagentic/verdict-store
Storage adapters: Postgres, SQLite, Cloudflare D1, R2/S3, filesystem, memory.
Part of Verdict — a serverless-first authorization engine. Policies (RBAC / ABAC / ReBAC) compile once and decide in microseconds, embedded in your app, behind a central PDP, or synced to the edge.
This package is the storage abstraction the control plane runs on: one ControlPlaneStore (SqlStore) over pluggable SQL drivers, plus an object-storage surface for bundle artifacts. Drivers are structurally typed, so the package depends on no database client — you pass your own pool or binding in.
Install
npm install @gomagentic/verdict-storeAdapters
| Backend | Adapter | Notes |
|---|---|---|
| Memory | MemoryStore, MemoryObjectStorage | Zero-dependency, for tests and local dev. |
| SQLite | SqlStore + NodeSqliteDriver | NodeSqliteDriver (Node ≥ 22, node:sqlite) ships from the /node subpath. |
| Cloudflare D1 | SqlStore + D1Driver | new D1Driver(env.DB) — sqlite dialect, no node:*. |
| Postgres | SqlStore + PostgresDriver | Wrap any pg-compatible Pool (PgPoolLike); pg is a peer you install. |
| Object storage | R2ObjectStorage, FsObjectStorage, ObjectBundleRepository | R2/S3 via R2BucketLike; FsObjectStorage ships from /node. ObjectBundleRepository layers the versioned bundle store over any ObjectStorage. |
SqlStore is a single ControlPlaneStore implementation over any SqlDriver. The driver carries its own dialect ("sqlite" covers SQLite files, in-memory, and D1; "postgres" covers Postgres and compatibles), and SqlStore rewrites ? placeholders to $1…$n for Postgres automatically.
Postgres example
PostgresDriver wraps any object with a pg-shaped query(text, values) — node-postgres, Neon serverless, pg-native. Install pg yourself (it is a peer, not a dependency of this package).
import { Pool } from "pg";
import {
PostgresDriver,
SqlStore,
applyMigrations,
POSTGRES_MIGRATIONS,
} from "@gomagentic/verdict-store";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const driver = new PostgresDriver(pool); // dialect: "postgres"
await applyMigrations(driver, POSTGRES_MIGRATIONS);
const store = new SqlStore(driver);
const tenant = await store.createTenant({ slug: "acme", displayName: "Acme" });
const policy = await store.getPolicy(tenant.id, "rbac", "billing");applyMigrations(driver, migrations) applies pending versions in order and returns the count applied; re-running is a no-op. On Postgres you also call driver.ensureAuditPartitions() from a maintenance schedule to keep the current and next month's audit_logs partitions in place.
Cloudflare (D1 + R2)
The main entry point pulls in no node:* modules, so it bundles cleanly for Workers. Build the store over the D1 binding and back bundle artifacts with an R2 bucket.
import {
D1Driver,
SqlStore,
R2ObjectStorage,
ObjectBundleRepository,
} from "@gomagentic/verdict-store";
export default {
async fetch(request: Request, env: Env) {
const store = new SqlStore(new D1Driver(env.DB)); // dialect: "sqlite"
const bundles = new ObjectBundleRepository(new R2ObjectStorage(env.BUNDLES));
const latest = await store.getLatestReadyBundle("acme_tenant_id");
// ...
return new Response("ok");
},
};D1Driver and R2ObjectStorage are structurally typed against D1DatabaseLike / R2BucketLike, so no @cloudflare/workers-types dependency is required — pass env.DB and env.BUNDLES straight in. For D1, apply the schema with wrangler d1 migrations apply (the migrations/ SQL ships in the package).
Node-only adapters live at a separate subpath so they never reach a Workers bundle:
import { NodeSqliteDriver, FsObjectStorage } from "@gomagentic/verdict-store/node";
const store = new SqlStore(new NodeSqliteDriver("./verdict.db"));
const storage = new FsObjectStorage("./data/bundles");Migrations
The schema is inlined at build time from migrations/<dialect>/*.sql and exported as SQLITE_MIGRATIONS and POSTGRES_MIGRATIONS (each a readonly Migration[]), so consumers get it with the package and Workers — which have no node:fs to read .sql files at runtime — can still apply it. applyMigrations(driver, migrations) records each applied version in schema_migrations with a checksum and fails loudly if an already-applied migration was edited. The raw migrations/ SQL is also shipped in the published package for tooling like wrangler d1 migrations apply.
Documentation
License
Apache-2.0
