Browser preview¶
The browser package runs the PostgreSQL kernel in Workers, with four independent SQL sessions and static pgvector, pg_trgm and hstore. Debug and optimized release wasm32 packages with pthreads have passed in Chromium, Firefox and Linux WebKit. This package is a source-built preview; an npm registry release has not been published. See the browser preview release notes for the accepted scope, data compatibility and release gates.
Try it on the homepage¶
The homepage starts a real upstream psql compiled
alongside the WebAssembly kernel. It starts automatically when the page loads;
once postgres=# appears, type directly into the terminal and press Enter:
CREATE DATABASE test;
\c test
CREATE TABLE t1(c1 integer, c2 text);
INSERT INTO t1 VALUES (100, 'hello');
SELECT * FROM t1;
\d t1
\?
While it starts, the compact terminal shows a centered loading indicator with an estimated percentage and elapsed time. The estimate follows startup stages; it is not a download byte counter. The first typed or pasted text expands the terminal once per page load, preserving your input. After you collapse it, typing and database resets keep it compact. You can also use the expand button at any time; a manual size choice disables automatic expansion until the page reloads.
Expanding also opens Examples beside the terminal; on a phone, use the
Examples button to open its drawer. Browse SQL aggregation, JSONB,
transactions, vector search and a parallel query. Insert puts the complete
script on an editable line without executing it; Run example executes it
through the same psql session and checks the result. Finish or clear existing
input and return to a standard idle prompt before running an example. Each
example cleans up its sample data; Ctrl+C interrupts the run and performs cleanup.
The parallel example requires EXPLAIN ANALYZE to report two planned and two
launched workers. Run quick check runs the four smaller examples and records
their outcomes, failed steps and timings in Report. These are interactive
smoke checks; release qualification uses the separate Wasm test gates. The examples
module loads on expansion, and sample data is created only when a script runs.
psql handles SQL parsing, prompts, backslash commands and result formatting.
The page provides an xterm.js terminal with inline input and output. Enter sends
a line; psql supplies the continuation prompt for unfinished SQL. The arrow keys
edit the current line or recall history; Home/End and Ctrl+A/E move within it.
Paste accepts multiple lines, including COPY FROM STDIN data. Ctrl+C interrupts
a query or clears unfinished input; Ctrl+L clears the screen. \q or Ctrl+D
exits; Enter starts a fresh session after exit. Close releases the instance
and Reset creates a fresh memory database. The SQL editor retains
the aggregation, JSONB, transaction and pgvector examples.
Use HTTPS or localhost to run the demo. GitHub Pages needs one automatic reload on first start to enable the browser's isolation requirements. The homepage preview does not save data to OPFS. Native terminal input/output queues are bounded to 64 KiB each, each input line is at most 16 KiB, pending input is capped at 64 KiB, and terminal scrollback is capped at 2,000 lines. Large results continue streaming while old displayed output is removed. Terminal assets are bundled with the site; no terminal CDN or backend service is needed.
Once the page content is ready, it imports the terminal and SDK's small JavaScript dependencies in parallel while the terminal starts. The page fonts are served with the site too, so rendering does not wait for an external font service. These changes work on GitHub Pages and do not alter the engine, its integrity checks or the fresh in-memory database created on every start.
If something fails, click Report in the terminal footer, then Copy report. The report opens inside the same window and includes the package build, browser capabilities, startup phase and timings (including navigation to the first psql prompt), cached runtime counters, and original error stacks. It remains available during startup and when the engine or terminal module fails to load; collecting it does not send a request to the database. Select Include recent SQL and terminal output when the problem needs query context (up to 200 terminal rows and 16,000 characters). This is off by default. Reports are generated locally and are never uploaded automatically. Review the text before sharing; if clipboard access is blocked, it can be selected and copied manually. Back to terminal preserves the session and unfinished input.
Use the OPFS persistence check to save a test record, restart your browser and verify that it survives. It also includes a reload with an uncommitted transaction and a copyable browser report.
This first psql integration is experimental and owns its database instance.
It uses the matching private libpq and PostgreSQL wire protocol over the existing
in-memory transport, all inside browser Workers. There is no remote SQL server,
WebSocket proxy, WebContainer or external shell. Shell/editor/pager commands and
network connection strings and signal-timer-based \watch are unavailable; readline completion and local file
upload/download are not implemented. Restarting psql creates a new instance.
The SDK's SQL connections, logical backup and data-directory archive operations
cannot run concurrently with a psql terminal in the same instance.
The same static assets can be hosted under a GitHub Pages repository path. The
service worker supplies isolation headers for the demo page and its assets;
all Worker/module/asset URLs are relative to that path. The one-command browser
check is make docs-live-check DOCS_WASM_PACKAGE=auto. It exercises real psql,
the SQL editor and the OPFS persistence check in Chromium, Firefox and WebKit on both a header-based host
and a headerless /project/ host. This emulates Pages; it does not publish a site.
For a local checkout, make docs-serve DOCS_WASM_PACKAGE=/path/to/package.tgz
bundles a source-built production package. Later make docs-serve invocations
reuse it. The preview server supplies isolation headers. When it runs remotely,
run make docs-serve DOCS_DEV_ADDR=0.0.0.0:8000 and open
https://SERVER_IP:8000/. Non-loopback binds automatically enable HTTPS and
generate a reusable self-signed development certificate. Copy
build/docs/tls/preview.crt to the computer running the browser and import it
into its trusted certificate authorities. Merely skipping the certificate
warning disables Chromium's HTTP cache and can make Worker startup time out on
slow connections. To supply a certificate already trusted by your browser,
set DOCS_TLS_CERT=/path/to/fullchain.pem and
DOCS_TLS_KEY=/path/to/key.pem. The default loopback address uses HTTP.
When HTTPS is enabled, HTTP requests to the same port automatically redirect
to HTTPS, preserving the page path and query string.
Build and install¶
From a source checkout with the native build prerequisites installed:
This initializes the source and pinned Wasm tools, generates the kernel and
builds a release browser SDK. Use WASM_MODE=debug for a debug package. The
command prints the exact postgamma-wasm-*.tgz path and does not run the full
browser test suite or publish the package.
make wasm-doctor checks the installed host compiler, SDK, npm dependencies
and target-header compatibility without installing or launching browsers.
wasm-build prepares these tools and runs that check automatically. For a
standalone check on a fresh checkout, run make source-init and
make wasm-toolchain-setup first. Browser startup can be checked separately
with make wasm-browser-doctor after make wasm-browser-setup.
To build and validate in one command, use make wasm-test JOBS=2. It runs the
full Wasm gates including the PGlite-derived tests in three browsers with
ephemeral and OPFS storage. Independent tests continue after failures; the
final exit status stays nonzero if any gate fails or cannot run.
make full-test JOBS=2 includes this suite after complete native validation.
Multi-hour endurance qualification remains make wasm-alpha-test.
The Wasm acceptance summary pointer is build/wasm/reports/ci.json; its
run_report names the full-suite report under build/wasm/ci/. Its sibling
package.log also records the package path. Install the tarball in your application:
Only building the package requires the SDK, Python and compiler. The installed package includes JS, TypeScript declarations, Worker modules, Wasm and database initialization resources, with no runtime dependencies on Arrow or other npm packages.
Serve the assets¶
Copy the complete node_modules/@postgamma/wasm/ directory to your application's
public directory, for example public/postgamma/. Preserve all relative paths,
including src/, postgamma.mjs, postgamma.wasm, postgamma.data, and
postgamma.assets.json. A bundler must retain this directory in its output.
The imported API and copied assets must come from the same package build.
Serve over HTTPS or localhost. The application page needs:
Use a JavaScript MIME type for .js and .mjs, and application/wasm for
.wasm. Host module Workers on the application's origin. Cross-origin page
resources must also satisfy the embedder policy. The loader reports missing
isolation, downloads, or mismatched core assets before opening a database.
With a bundler, import the package normally. For an unbundled page, add this import map before the module script:
Query and close¶
import {Database} from '@postgamma/wasm';
const db = await Database.open({
assetBase: new URL('/postgamma/', location.href),
storage: {type: 'opfs', name: 'documents'},
});
const connection = await db.connect();
try {
const results = await connection.query('SELECT $1::int8 AS value', {
parameters: [{typeOid: 20, value: '9223372036854775807'}],
});
console.log(results[0].rows);
} finally {
await connection.close();
await db.close();
}
Omit storage for an ephemeral database. Closing it discards its data; opening
another ephemeral database creates a fresh cluster. OPFS data belongs to the
origin and database name. Reopen it with the same name and create: false to
require an existing database. One origin/name has one live owner, enforced with
Web Locks and a synchronous file access handle. Browser site-data deletion or
origin changes make the old database unavailable. Native PostgreSQL cluster
directories cannot be mounted into this target. OPFS uses best-effort storage
by default: automatic browser eviction can remove an origin's data under
storage pressure or inactivity policies. Check or explicitly request persistence:
const state = await Database.storagePersistence();
// Call in response to an application action when persistent storage is needed.
const requested = await Database.storagePersistence({request: true});
// 'persistent', 'best-effort' (including a denied request), or 'unsupported'
This permission applies to the origin, not to one database. open() never
requests it automatically. It does not protect against user deletion of site
data or replace backups. Safari's inactivity policy counts seven days of
Safari use without interaction with the site, not simply seven calendar days;
installed web apps have different treatment. See WebKit's storage policy
and inactivity policy.
Opening an occupied OPFS database waits up to 5 seconds for the previous owner
to release it. Set lockTimeoutMs to change that bound, or lock: 'fail' to
reject immediately. A zero timeout also makes one immediate attempt. The bound
applies to acquiring ownership, not the subsequent kernel startup. A waiting
open creates only the control Worker; it allocates no Wasm memory or pthreads
until it owns the database. Pass signal to cancel an opening operation; after
open() resolves, the signal no longer controls that database.
import {Database, DatabaseLockedError} from '@postgamma/wasm';
try {
const db = await Database.open({
storage: {type: 'opfs', name: 'documents'},
lockTimeoutMs: 5000,
signal: new AbortController().signal,
});
await db.close();
} catch (error) {
if (error instanceof DatabaseLockedError) {
console.log(error.databaseName, error.reason); // 'busy' or 'timeout'
} else throw error;
}
Tabs do not share an instance or its SQL sessions. Another tab must wait for
the current owner to close; it never steals ownership. Close the database
before application-controlled navigation when possible. In a Window, OPFS
instances default to bfcache: 'discard': on pagehide with persisted: true,
the SDK invalidates the instance and terminates its control/pthread Workers
without awaiting asynchronous callbacks in the freezing page. Back/Forward
restoration requires opening a new instance; old handles reject with
ClosedError. Browser lock and file-handle release remains asynchronous, so
reopening still waits by default. WAL recovers acknowledged commits; an
interrupted COMMIT with no reply has an unknown outcome and must be checked
before retrying.
Memory instances default to bfcache: 'preserve'. Explicitly preserving an
OPFS instance leaves navigation/ownership coordination to the application;
explicitly discarding a memory instance loses its data. The policy can be set
with Database.open({storage: ..., bfcache: 'discard'}). If the SDK itself
runs in an application Worker, coordinate closing from its host page before
navigation: Workers cannot observe Window lifecycle events. The cross-tab
gate records real bfcache restoration separately from synthetic pagehide tests.
Use await Database.list() to enumerate named OPFS clusters, including failed
restores. It returns a sorted snapshot without opening a kernel. Use
await Database.delete(name, {lock: 'fail'}) for safe removal; it returns false
if absent. Deletion takes the same lock as open/restore, accepts the same lock
timeouts and an AbortSignal, and retains ownership until removal completes.
Cancellation only applies before removal starts. Do not remove OPFS directories
directly. Cluster names here are distinct from SQL database names.
Synchronous COMMIT waits for the browser storage barrier. The Chromium recovery
suite covers interrupted Workers/browser processes and storage failures; it
does not establish hardware power-loss durability. Handle quota and I/O errors
and reopen a discarded instance before further work.
make wasm-multitab-check WASM_MODE=release additionally checks real tabs,
waiting/cancellation, refresh, navigation, and tab termination during WAL flush
in Chromium, Firefox and Linux WebKit. It runs as part of make wasm-test.
Metadata checkpoints reclaim the history of file creation, deletion and rename. The profile supports 16,384 live file/directory entries and keeps its two metadata files within 16 MiB in normal operation. Existing preview databases upgrade automatically when opened; that migration needs up to 5 MiB of extra space for a snapshot. Older builds cannot open the upgraded storage format.
For a complete same-build copy, use dumpDataDir() with Database.loadDataDir(),
or clone(). Export closes an idle source database; close its connections and
requests first. Restore requires a new target and the exact matching asset
build. Failed OPFS restores retain an incomplete marker and cannot be opened;
delete the failed target with Database.delete(name) before reusing that name.
An export retains ownership until its stream reaches EOF, is cancelled, or
db.close() completes. To bound an abandoned stream, pass
dumpDataDir({idleTimeoutMs: 30000}); it fails with TimeoutError after 30
seconds without a pull and starts cleanup. The default is disabled. Time spent
waiting for an active pull is excluded, but slow consumer backpressure counts
as idle. await db.close() waits for cleanup and exposes cleanup errors.
Each connection serializes query() calls; different connections can execute
concurrently. Use execute() and its request iterator for large results,
cancel() for cancellation, and COPY streams for bulk data. Values preserve
exact int64/numeric strings, NULL and binary bytes. Errors retain SQLSTATE.
Connection startup starts instance discard at its 90-second deadline if it cannot complete or retire. Callers are rejected after the cleanup attempt, and all connections on that instance become unusable. Open a replacement before issuing further work. Cleanup failures remain visible in errors and diagnostics.
Run CREATE EXTENSION vector, CREATE EXTENSION pg_trgm or
CREATE EXTENSION hstore once per database to enable the included modules. The
examples/wasm/vector-search.html is available from the
source checkout with make wasm-vector-serve.
Parallel queries¶
A connection can opt into PostgreSQL parallel query execution:
The supported profile is four independent sessions sharing at most two parallel
workers, in addition to each query's leader. Configure every participating
connection this way. New connections start with parallel workers disabled.
The planner chooses parallel execution when its costs justify it; inspect
EXPLAIN (ANALYZE, FORMAT JSON) for Workers Launched, since a parallel plan
can fall back to its leader when both workers are occupied. The worker limit is
shared with parallel index maintenance; larger worker counts are outside this
profile. Keep parallel_leader_participation at its default of on.
make wasm-parallel-check WASM_MODE=release exercises parallel scans, partial
aggregates, parallel hash joins and Gather Merge in Chromium, Firefox and
Linux WebKit with ephemeral and OPFS storage. Every accepted parallel plan must
show Workers Planned: 2 and Workers Launched: 2 in EXPLAIN ANALYZE, including
with leader participation disabled and after OPFS reopen. It compares complete ordered
results with serial execution and checks snapshots, session settings,
cancellation, statement timeout, worker errors, worker-budget fallback,
persistent reopen and resource release. make wasm-test includes this gate;
its standalone report pointer is build/wasm/reports/parallel.json.
Measured scope¶
make wasm-browser-setup wasm-matrix-check WASM_MODE=release reuses the pinned browsers
and measures the previously validated package. Reports are under
build/wasm/matrix/; each engine runs SQL, four concurrent sessions,
transactions, cancellation, vector indexes and separate ephemeral/OPFS reopen
cases. All three engines are required gates. OPFS is tested with normal
persistent browser profiles; private browsing can disable its storage APIs.
The complete storage error and crash-recovery suite remains a Chromium gate.
The shared linear-memory ceiling is 512 MiB with a fixed 14-slot pthread pool. JS heap, compilation memory and browser VM/RSS are additional and are not measured by that ceiling. The timings below are local release observations, not performance guarantees. Safari on macOS/iOS, mobile browsers, Node.js and WASI execution have not been accepted. Multiple live instances and arbitrary native extension loading are outside the initial profile.
Long mixed-load runs currently exceed the 128 MiB browser memory-growth budget.
Functional regression passes do not complete this resource acceptance. Use
make wasm-soak-test for the two-hour gate in each of the three engines; release
remains blocked until that gate passes with the original limits.
To validate an optimized archive, run make wasm-release JOBS=2 after toolchain
setup. This runs the same complete SQL, vector, recovery and browser gates with
-O2; reports and artifacts identify their build mode. OPFS requires Web Locks,
BroadcastChannel and synchronous file access handles in addition to the page's
isolation headers.
Measured on Linux x86-64 on 2026-09-13, with Playwright 1.63.0 and the
0.1.0-alpha.1 release package (-O2) built from PostgreSQL 19beta3 and pgvector 0.8.6:
| Engine | Ephemeral package profile | OPFS package profile |
|---|---|---|
| Chromium 153.0.8010.12 | Passed | Passed |
| Firefox 155.0 | Passed | Passed |
| Linux WebKit 26.6 | Passed | Passed |
The npm archive was 3,592,432 bytes (3.43 MiB), with 12,187,668 unpacked bytes
(11.62 MiB), including notices and declarations. The representative query was
SELECT sum(i) FROM generate_series(1,10000) i, sampled 20 times after startup.
| Engine / storage | Create | Reopen | Query median / p95 | Peak shared linear memory |
|---|---|---|---|---|
| Chromium / ephemeral | 3.64 s | 3.49 s | 21.53 / 32.93 ms | 184.38 MiB |
| Chromium / OPFS | 14.25 s | 1.25 s | 18.97 / 21.05 ms | 128.00 MiB |
| Firefox / ephemeral | 3.66 s | 3.57 s | 23.02 / 34.32 ms | 184.38 MiB |
| Firefox / OPFS | 36.82 s | 1.95 s | 21.85 / 27.40 ms | 128.00 MiB |
| Linux WebKit / ephemeral | 3.57 s | 3.83 s | 24.11 / 31.28 ms | 184.38 MiB |
| Linux WebKit / OPFS | 11.66 s | 1.42 s | 19.96 / 22.90 ms | 128.00 MiB |
Ephemeral reopen initializes another fresh cluster; OPFS reopen opens existing
files. Peaks cover the full SQL/vector workload and are not idle memory sizes.
These are single local runs without exclusive host access; browser caches,
host load and engine scheduling affect timings. The complete record is
build/wasm/matrix/20260913T033050Z-e3cb95ed/report.json, including package and
harness hashes, raw samples and version identities. Earlier failed rounds remain
in their own report directories.