Multi-tab shared database access through dedicated or shared worker threads, enabling concurrent database operations across browser tabs.
import { PGliteWorker, worker } from "@electric-sql/pglite/worker";Client-side class for connecting to a PGlite database running in a worker thread.
/**
* Multi-tab shared database client extending BasePGlite
* Connects to PGlite database running in a worker thread
*/
class PGliteWorker extends BasePGlite {
constructor(worker: Worker, options?: PGliteWorkerOptions);
/** Promise resolving when worker connection is ready */
readonly waitReady: Promise<void>;
/** Whether the worker connection is ready */
readonly ready: boolean;
/** Whether the worker connection is closed */
readonly closed: boolean;
/** Data directory path from worker */
readonly dataDir?: string;
}Server-side function to set up PGlite in a worker thread.
/**
* Setup function for worker thread side
* Call this in your worker script to handle PGlite operations
* @param options - Worker configuration options
*/
function worker(options?: WorkerOptions): void;Error thrown when the worker leader changes during multi-tab scenarios.
/**
* Error thrown when worker leader changes
* Indicates that another tab has taken control
*/
class LeaderChangedError extends Error {
constructor(message?: string);
}Configuration options for PGlite worker setup.
interface WorkerOptions {
/** PGlite initialization options */
pgliteOptions?: PGliteOptions;
/** Custom message handler */
messageHandler?: (message: any) => Promise<any>;
/** Error handler */
errorHandler?: (error: Error) => void;
}
interface PGliteWorkerOptions<TExtensions = Record<string, Extension>> {
/** Worker instance */
worker?: Worker;
/** Debug level */
debug?: DebugLevel;
/** Extension configuration */
extensions?: TExtensions;
/** Connection timeout in milliseconds */
connectionTimeout?: number;
/** Retry configuration */
retryConfig?: {
maxRetries: number;
retryDelay: number;
};
}Internal message types used for worker communication.
interface WorkerMessage {
/** Unique message ID */
id: string;
/** Message type */
type: 'query' | 'exec' | 'transaction' | 'close' | 'listen' | 'unlisten';
/** Message payload */
payload: any;
}
interface WorkerResponse {
/** Message ID this responds to */
id: string;
/** Whether the operation succeeded */
success: boolean;
/** Response data or error */
data: any;
}type WorkerExtensionSetup<TNamespace = any> = (
pg: PGliteInterface,
options: any
) => Promise<TNamespace>;Usage Examples:
// worker.js
import { worker } from "@electric-sql/pglite/worker";
import { PGlite } from "@electric-sql/pglite";
import { live } from "@electric-sql/pglite/live";
import { vector } from "@electric-sql/pglite/vector";
// Setup PGlite in worker
worker({
pgliteOptions: {
dataDir: "shared-database",
extensions: {
live,
vector,
},
},
errorHandler: (error) => {
console.error("Worker error:", error);
},
});// main.js
import { PGliteWorker } from "@electric-sql/pglite/worker";
// Create worker
const workerInstance = new Worker("./worker.js");
// Connect to worker database
const db = new PGliteWorker(workerInstance, {
debug: 1,
connectionTimeout: 5000,
retryConfig: {
maxRetries: 3,
retryDelay: 1000,
},
});
// Wait for connection
await db.waitReady;
// Use like regular PGlite
const results = await db.query("SELECT * FROM users");
console.log(results.rows);
// Transactions work across worker boundary
const transactionResult = await db.transaction(async (tx) => {
await tx.query("INSERT INTO users (name) VALUES ($1)", ["Alice"]);
return tx.query("SELECT * FROM users");
});
// Listen for notifications
const unlisten = await db.listen("user_updates", (payload) => {
console.log("User updated:", payload);
});
// Cleanup
await unlisten();
await db.close();// shared-worker.js
import { worker } from "@electric-sql/pglite/worker";
import { IdbFs } from "@electric-sql/pglite";
// Setup shared database
worker({
pgliteOptions: {
dataDir: "multi-tab-db",
fs: new IdbFs("multi-tab-db"),
},
});// Each tab connects to shared worker
const sharedWorker = new SharedWorker("./shared-worker.js");
const db = new PGliteWorker(sharedWorker.port);
await db.waitReady;
// All tabs share the same database state
await db.query("CREATE TABLE IF NOT EXISTS shared_data (id SERIAL, value TEXT)");
await db.query("INSERT INTO shared_data (value) VALUES ($1)", [`Tab ${Date.now()}`]);
const allData = await db.query("SELECT * FROM shared_data");
console.log("Shared data across tabs:", allData.rows);import { PGliteWorker, LeaderChangedError } from "@electric-sql/pglite/worker";
const db = new PGliteWorker(worker);
try {
await db.query("SELECT * FROM users");
} catch (error) {
if (error instanceof LeaderChangedError) {
console.log("Leader changed, reconnecting...");
// Handle leader change - possibly reconnect
await db.close();
const newDb = new PGliteWorker(new Worker("./worker.js"));
await newDb.waitReady;
} else {
console.error("Database error:", error);
}
}// worker.js with custom message handling
import { worker } from "@electric-sql/pglite/worker";
worker({
pgliteOptions: {
dataDir: "./data",
debug: 2,
relaxedDurability: true,
},
messageHandler: async (message) => {
// Custom message processing
if (message.type === 'custom-operation') {
// Handle custom operations
return { result: 'custom-handled' };
}
// Return null to use default handling
return null;
},
errorHandler: (error) => {
// Custom error logging
console.error(`[Worker] ${error.message}`, error);
},
});