or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

database-operations.mdextensions.mdfilesystem-storage.mdindex.mdlive-queries.mdsql-templates.mdvector-operations.mdworker-support.md
tile.json

worker-support.mddocs/

Worker Support

Multi-tab shared database access through dedicated or shared worker threads, enabling concurrent database operations across browser tabs.

Import

import { PGliteWorker, worker } from "@electric-sql/pglite/worker";

Capabilities

PGliteWorker Class

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;
}

Worker Setup Function

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;

Leader Changed Error

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);
}

Worker Options

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;
  };
}

Worker Message Protocol

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;
}

Types

type WorkerExtensionSetup<TNamespace = any> = (
  pg: PGliteInterface,
  options: any
) => Promise<TNamespace>;

Usage Examples:

Worker Script Setup

// 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);
  },
});

Client-Side Usage

// 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();

Multi-Tab Shared Database

// 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);

Error Handling

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);
  }
}

Advanced Worker Configuration

// 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);
  },
});