PGlite
PGlite is Postgres compiled to WebAssembly, running inside your process with no server, container or port. UQL treats it as Postgres. Use it for tests, where each file gets a fresh database in milliseconds, and for local development on your production dialect.
Install and connect
Section titled “Install and connect”npm install uql-orm @electric-sql/pgliteimport { PgliteQuerierPool } from 'uql-orm/pglite';
export const pool = new PgliteQuerierPool(); // in memoryPass a directory to keep the data, and PGlite’s own options second. On disk, relaxedDurability stops PGlite from waiting on a flush after every statement inside a transaction; the data is still written:
const pool = new PgliteQuerierPool('file://./pgdata', { relaxedDurability: true,});Vector search
Section titled “Vector search”pgvector is a separate bundle that PGlite loads at startup:
npm install @electric-sql/pglite-pgvectorimport { vector } from '@electric-sql/pglite-pgvector';
const pool = new PgliteQuerierPool('memory://', { extensions: { vector } });Vector search then works as on a server, halfvec and sparsevec included. The extension pins an exact @electric-sql/pglite version, so upgrade both together.
One connection, and what follows from it
Section titled “One connection, and what follows from it”PGlite has a single connection that every querier shares. A transaction holds the whole database, and other queriers wait until it ends. Give work that must run concurrently its own pool, and so its own database; a test suite or a single-user dev session is unaffected.
$lock emits SELECT ... FOR UPDATE, but no second transaction can contend with it, so test row-locking behaviour on a Postgres server. findManyStream pages through a DECLARE/FETCH cursor, inside a transaction of its own if you have none, which blocks the database until the loop ends.
Differences from uql-orm/postgres
Section titled “Differences from uql-orm/postgres”A BYTEA column reads back as a Uint8Array, not a Buffer, so instanceof Buffer fails. A statement binds at most 32,767 values, so UQL splits a large insertMany into smaller chunks. Migration runs hold their lock as a uql/lock record, since queriers share a session.
Otherwise the two are interchangeable: both report the postgres dialect, so migrations and drift:check carry across.