TypeScript guide

Use parameters, transactions, persistent storage, extensions, and backups in native JavaScript apps.

These recipes use an open db from the TypeScript quickstart. Keep one handle in your application service and close it during shutdown.

Query application data

Create the table before running the insert and read:

await db.execute(`
  CREATE TABLE IF NOT EXISTS notes (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    body text NOT NULL
  )
`);

const inserted = await db.query(
  'INSERT INTO notes (body) VALUES ($1) RETURNING id',
  ['First note'],
);
const notes = await db.query('SELECT id, body FROM notes ORDER BY id');
console.log(notes.rows);

Use parameters for values; do not interpolate user input into SQL. Use query when you need rows, execute when you need command metadata, and exec for a trusted SQL script containing multiple statements.

Run a transaction

Use the callback's tx object for every statement in the transaction:

await db.transaction(async (tx) => {
  await tx.execute('INSERT INTO notes (body) VALUES ($1)', ['First']);
  await tx.execute('INSERT INTO notes (body) VALUES ($1)', ['Second']);
});

Returning commits; throwing rolls back. The callback's return value becomes the transaction result. Do not call the outer db or send manual BEGIN, COMMIT, or full ROLLBACK commands inside the callback. Use tx.rollback() for an explicit rollback. Savepoints are supported.

Select extensions

Install the native extension package and import its descriptor:

npm install @oliphaunt/extension-vector@0.3.0
import vector from '@oliphaunt/extension-vector';

const db = await Oliphaunt.open({ extensions: [vector] });
await db.execute('CREATE EXTENSION IF NOT EXISTS vector');

The extension must match the native runtime and target platform. See Extensions for the selection rules and catalog.

Back up and restore

backup() returns a physical archive as a Uint8Array. Save or transfer those bytes using your host's file APIs. Restore to a new or empty directory:

const archive = await db.backup();
await db.close();
await Oliphaunt.restore(
  { kind: 'directory', path: './app-data/restored.oliphaunt' },
  archive,
);

const restored = await Oliphaunt.open({
  topology: 'broker',
  storage: { kind: 'directory', path: './app-data/restored.oliphaunt' },
});
try {
  console.log((await restored.query('SELECT count(*) FROM notes')).rows);
} finally {
  await restored.close();
}

The restored handle uses broker mode because direct mode remains bound to its original root for the lifetime of the process, even after close. Restore does not replace a nonempty database. Open with the same required extension selection. Use logical tools to move between native and WASIX.

Choose a runtime mode

Direct mode is the default. Select broker mode for a separate helper process:

const db = await Oliphaunt.open({
  topology: 'broker',
  storage: { kind: 'directory', path: './app-data/main.oliphaunt' },
});

Use a server when a driver or ORM needs independent PostgreSQL sessions:

const server = await Oliphaunt.openServer({
  storage: { kind: 'directory', path: './app-data/server.oliphaunt' },
});
console.log(server.connectionString);
// Keep server in application state while clients are connected.
// Close clients and pools before awaiting server.close().

The server handle owns the process; a copied connection string does not keep it alive. The separate @oliphaunt/tools package provides endpoint-oriented PostgreSQL tools. See runtime modes.

Handle errors and shutdown

PostgreSQL errors expose SQLSTATE through PostgresError. Use the code to distinguish expected database failures such as a constraint violation from runtime or storage failures. A transaction callback that throws is rolled back; if rollback also fails, the SDK retains both errors in an AggregateError.

Call db.cancel() to interrupt active database work. Then await the operation and handle its result. Close rejects new work and drains work already accepted. Always await close(); garbage collection does not provide observable cleanup.

After a broker crash or an unrecoverable protocol failure, close the handle and reopen persistent storage. Do not automatically replay an operation whose commit outcome is unknown.

Package an Electron app

Keep .node modules, helper executables, and their runtime resources outside the ASAR archive. Preserve their package-relative layout so the resolver can find them. Test a packaged application on each target architecture; a development-server run does not exercise packaging.

Troubleshooting

SymptomCheck
Native module fails to loadRuntime, operating system, architecture, and extracted package files
Storage is lockedAnother live handle or process owns the same root
Extension is unavailableThe extension package is installed and selected before open
A transaction waits indefinitelyAll callback SQL uses tx, not the outer db
Queries serializeOne embedded handle has one session; choose server mode for a pool