Vex in a page
Postgres built to run in a browser, through the same adapter, with no server. It is how this site runs Vex.
pnpm add @niscorp/vex @niscorp/prism @niscorp/strata zod @electric-sql/pglite vitePostgres in the browser
This file is a page’s entry, not a script. PGlite is Postgres compiled to WebAssembly, and @niscorp/vex and @niscorp/vex/pglite import no Node built-in, so a bundler builds them for a page with no alias and no polyfill.
import { PGlite } from '@electric-sql/pglite';
import { createPostgresAdapter, createQueryEngine, seedCache } from '@niscorp/vex';
import { createPglitePool } from '@niscorp/vex/pglite';
const db = new PGlite();
await db.exec(`
CREATE TABLE bookings (id text PRIMARY KEY, night text, member text, seats integer);
INSERT INTO bookings VALUES ('B-2040', '2026-10-24', 'Ada', 1), ('B-2041', '2026-10-24', 'Grace', 2);
`);
const engine = createQueryEngine({ adapter: createPostgresAdapter({ pool: createPglitePool(db) }) });
await engine.introspect();A read, seeded
The read is seeded as it would be on a server. Vex’s hashes are the same in a page as on a server, so an entry seeded at build time replays in the browser.
await seedCache(engine.cache, [{
fingerprint: 'boxoffice/coming',
dsl: {
from: ['bookings'],
fields: ['bookings.member', 'bookings.seats'],
filter: { eq: ['bookings.night', { $context: 'night' }] },
},
}]);Show it
The answer goes onto the page. Put the file beside an index.html with <script type="module" src="/main.ts"></script> and run npx vite. PGlite cannot enforce a statement timeout, and Vex says so once in the console.
const response = await engine.execute({ fingerprint: 'boxoffice/coming', context: { night: '2026-10-24' } });
document.body.textContent = JSON.stringify(response.result);Run it.
The page shows [{"member":"Ada","seats":1},{"member":"Grace","seats":2}]. A browser is not a check, so nothing here is printed by one.
npx vite