Skip to content

Coming from PGlite

pgwasm began as a fork of PGlite and keeps its query model: the same query, exec, sql and transaction calls, the same result shape, live queries and a Drizzle driver. This page is for an app that uses PGlite today without PGlite extensions and is weighing a move to @pgxsinkit/pgwasm and @pgxsinkit/pgwasm-c. pgwasm works on its own; the rest of pgxsinkit is not needed.

Terminal window
bun add @pgxsinkit/pgwasm @pgxsinkit/pgwasm-c

An app that builds with Vite keeps PGlite out of dependency pre-bundling. Name both packages there instead:

vite.config.ts
export default defineConfig({
optimizeDeps: { exclude: ["@pgxsinkit/pgwasm", "@pgxsinkit/pgwasm-c"] },
});

With PGlite:

db.ts (PGlite)
import { PGlite } from "@electric-sql/pglite";
import { live } from "@electric-sql/pglite/live";
import { drizzle } from "drizzle-orm/pglite";
export const pg = await PGlite.create({ dataDir: "idb://my-app", extensions: { live } });
export const db = drizzle({ client: pg });

With pgwasm:

db.ts (pgwasm)
import { createPgwasm } from "@pgxsinkit/pgwasm";
import { drizzle } from "@pgxsinkit/pgwasm/drizzle";
import { live } from "@pgxsinkit/pgwasm/live";
import { cBuild } from "@pgxsinkit/pgwasm-c";
export const pg = await createPgwasm({ build: cBuild, dataDir: "idb://my-app", extensions: { live } });
export const db = drizzle({ client: pg }); // or drizzle(pg)

Code that uses the database stays as it was:

const { rows } = await pg.query<{ id: number; title: string }>("SELECT id, title FROM todo WHERE done = $1", [false]);
const open = await db.select().from(todo).where(eq(todo.done, false));
const watched = await pg.live.query("SELECT count(*) FROM todo", [], (results) => render(results.rows));

createPgwasm is always async and resolves once the database is ready; there is no constructor. build is the compiled Postgres it runs: cBuild is PostgreSQL 18 built with Emscripten, the same kind of build PGlite ships (see Postgres builds).

PGlite pgwasm
new PGlite(options), PGlite.create(options) await createPgwasm({ build: cBuild, ...options })
query, sql, exec, transaction the same, with the same results (rows, fields, affectedRows, blob)
query options rowMode, parsers, serializers, blob, onNotice, paramTypes the same
listen, unlisten, onNotification the same; to remove a callback, call the function onNotification returns (no offNotification)
dumpDataDir(), the loadDataDir option the same; a backup restores only into an empty data directory (DataDirExistsError)
waitReady, ready, closed, close(), refreshArrayTypes() the same
extensions: { live } from @electric-sql/pglite/live @pgxsinkit/pgwasm/live: live.query, live.incrementalQuery, live.changes; PgwasmWithLive
drizzle-orm/pglite: drizzle(pg), drizzle({ client: pg }) @pgxsinkit/pgwasm/drizzle, both forms
execProtocol, execProtocolRaw, execProtocolStream, … protocol(pg) from @pgxsinkit/pgwasm/protocol
pgDump from @electric-sql/pglite-tools/pg_dump pgDump from @pgxsinkit/pgwasm-pg-dump (pg, args, fileName)
Repl from @electric-sql/pglite-repl Repl from @pgxsinkit/pgwasm-repl (React 19.3 or later)
dataDir() from @electric-sql/pglite-prepopulatedfs prepopulatedDataDir() from @pgxsinkit/pgwasm-c/prepopulated
amcheck from @electric-sql/pglite/contrib/amcheck amcheck from @pgxsinkit/pgwasm-c/contrib/amcheck

The Drizzle driver needs drizzle-orm 1.0 (the release its peer dependency names), and it never opens a database of its own: drizzle(), drizzle("…") and drizzle({ connection }) throw UnsupportedDrizzleConfigError. Create the database with createPgwasm and pass it in.

dataDir needs a scheme. memory:// (or no dataDir) is in memory, idb://<name> is IndexedDB, and file://<path> is a directory on disk (Bun). A bare path, which PGlite read as a directory, fails with UnsupportedDataDirError: write file://./data for ./data. dataDir and fs are exclusive.

Some options moved to the build or to settings.

PGlite option pgwasm
pgliteWasmModule (earlier wasmModule), initdbWasmModule, fsBundle createCBuild({ postgresWasmModule, initdbWasmModule, fsBundle }), or all three warmed together in createCBuild({ assets }); pass the result as build
-c name=value in startParams, lines of postgresqlconf settings: { name: "value" }
initialMemory, initDbStartParams, noInitDb, icuDataDir not options

See warming the artefacts early for createCBuild({ assets }).

Some methods are gone.

  • clone(): dump and restore instead, await createPgwasm({ build: cBuild, loadDataDir: await pg.dumpDataDir() }).
  • describeQuery(): no equivalent.
  • syncToFs(): every statement persists before it returns, or in the background with relaxedDurability; the OPFS store’s strict boundary on demand is strictSync(pg).
  • offNotification(), isInTransaction(), copyToFS(): not on pgwasm.

One open database per store. An idb:// store already open in another context (another tab or worker) fails with StorageInUseError.

Typed errors. Every runtime error extends PgwasmError, and a statement Postgres rejects throws DatabaseError with its code and fields. See the error list.

Existing stores are not carried over. Stores created by PGlite or by an earlier pgxsinkit release are not supported by this release: refresh them. pgxsinkit’s local sync stores are refreshed by destroying the local store (client.destroy()) and letting it re-sync from the server. An idb://<name> store needs nothing: pgwasm names its IndexedDB database /pgwasm/<name> where PGlite and earlier pgxsinkit releases used /pglite/<name>, so the same idb://<name> opens a new, empty store. Data held only in the old store does not come across: writes that had not synced when the app upgraded stay behind. Let pending writes flush before upgrading, or take a dumpDataDir() backup.

A backup carries a database across. Take a dumpDataDir() backup with the old version and pass it as loadDataDir when creating the pgwasm database: the data directory format is unchanged. A PGlite 0.5 backup restores too: PGlite 0.5 runs PostgreSQL 18.3, and the C build runs a later PostgreSQL 18 release, which reads the same data directory. A data directory PGlite wrote carries no build record, and the C build claims it (see A store belongs to its build).

const backup = await oldPg.dumpDataDir(); // on PGlite 0.5, or an earlier pgxsinkit release
const pg = await createPgwasm({ build: cBuild, dataDir: "idb://my-app", loadDataDir: backup });

The old databases stay until you delete them. pgwasm never deletes /pglite/* IndexedDB databases: PGlite uses the same names, and another app on the same origin may own them. Once an app’s own old stores are no longer needed, it can delete them by name, or list them with indexedDB.databases() where the browser supports it:

indexedDB.deleteDatabase("/pglite/my-app"); // the store that was idb://my-app

Earlier PGlite versions need a dump and restore. PGlite 0.2 runs PostgreSQL 16 and PGlite 0.3 and 0.4 run PostgreSQL 17; PostgreSQL 18 does not open their data directories. Dump with PGlite’s pg_dump and run the SQL into a new pgwasm database:

import { pgDump } from "@electric-sql/pglite-tools/pg_dump";
const dump = await pgDump({ pg: oldPg }); // on PGlite
const pg = await createPgwasm({ build: cBuild, dataDir: "idb://my-app-v2" });
await pg.exec(await dump.text());

A plain dump leaves the restoring session’s search_path empty; set it back if the session relies on it.

opfs-ahp:// stores need a backup. pgwasm does not read PGlite’s one-handle-per-file OPFS layout: opfs-ahp:// fails with OpfsAhpRemovedError. Take a dumpDataDir() backup on PGlite 0.5 (a SQL dump on earlier versions) and pass it as loadDataDir when creating the pgwasm database. pgwasm’s OPFS store is createOpfsPgwasm from @pgxsinkit/pgwasm/opfs, which runs in a worker and keeps a data directory in four files (see the OPFS-repacked store):

import { createOpfsPgwasm } from "@pgxsinkit/pgwasm/opfs";
const backup = await oldPg.dumpDataDir(); // on PGlite 0.5
const pg = await createOpfsPgwasm({ build: cBuild, directory, pgwasm: { loadDataDir: backup } });
  • Runtimes. pgwasm is ESM only, for Bun and browsers (pages, dedicated workers, SharedWorkers and extension pages). Node, Deno and CommonJS are not supported.
  • Browsers. The C build runs in Safari and iOS 18.4, Chrome 137, Firefox 131 and later releases (see Supported browsers).
  • Extensions. amcheck is the only extension. PGlite’s other contrib modules (such as citext, bloom or cube) and its separately published extensions (such as pgvector or PostGIS) are not available.
  • Workers. There is no drop-in equivalent of PGliteWorker. pgxsinkit’s worker mode runs the local store of its own sync client, not a general-purpose pgwasm database.
  • Framework bindings. pgwasm has no React or Vue bindings of its own like @electric-sql/pglite-react or @electric-sql/pglite-vue. pgxsinkit’s React hooks serve its sync client.