Multi-tab Worker
It's likely that you will want to run PGlite in a Web Worker so that it doesn't block the main thread. Additionally, as PGlite is single connection only, you may want to proxy multiple browser tabs to a single PGlite instance.
To aid in this, we provide a PGliteWorker
with the same API as the standard PGlite, and a worker
wrapper that exposes a PGlite instance to other tabs.
Using PGliteWorker
First, you need to create a js file for your worker instance. You use the worker
wrapper with an init
option that returns a PGlite instance to start that database and expose it to all tabs:
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'
worker({
async init() {
// Create and return a PGlite instance
return new PGlite()
},
})
Then connect the PGliteWorker
to your new worker process in your main script:
import { PGliteWorker } from '@electric-sql/pglite/worker'
const pg = new PGliteWorker(
new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
type: 'module',
}),
)
// `pg` has the same interface as a standard PGlite interface
Internally, this starts a worker for each tab, but then runs an a election to nominate one as the leader. Only the leader then starts PGlite by calling the init
function, and handles all queries. When the leader tab is closed, a new election is run, and a new PGlite instance is started.
In addition to having all the standard methods of the PGlite
interface, PGliteWorker
also has the following methods and properties:
onLeaderChange(callback: () => void)
This allows you to subscribe to a notification when the leader worker is changed. It returns an unsubscribe function.offLeaderChange(callback: () => void)
This allows you to unsubscribe from the leader change notification.isLeader: bool
A boolean property indicating if this instance is the leader.
Passing options to a worker
PGliteWorker
takes an optional second parameter options
; this can include any standard PGlite options along with these additional options:
id: string
This is an optionalid
to group your PGlite workers. The leader election is run between allPGliteWorker
s with the sameid
.
If not provided, the url to the worker is concatenated with thedataDir
option to create an id.meta: any
Any additional metadata you would like to pass to the worker processinit
function.
The worker()
wrapper takes a single options argument, with a single init
property. init
is a function that takes any options passed to PGliteWorker
, excluding extensions, and returns a PGlite
instance. You can use the options passed to decide how to configure your instance:
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'
worker({
async init(options) {
const meta = options.meta
// Do something with additional metadata.
// or even run your own code in the leader along side the PGlite
return new PGlite({
dataDir: options.dataDir,
})
},
})
// my-app.js
import { PGliteWorker } from '@electric-sql/pglite/worker'
const pg = new PGliteWorker(
new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
type: 'module',
}),
{
dataDir: 'idb://my-db',
meta: {
// additional metadata passed to `init`
},
},
)
Extension support
PGliteWorker
has support for both Postgres extensions and PGlite plugins using the normal extension api.
Any extension can be used by the PGlite instance inside the worker, however the extensions namespace is not exposed on a connecting PGliteWorker
on the main thread.
// my-pglite-worker.js
import { PGlite } from '@electric-sql/pglite'
import { worker } from '@electric-sql/pglite/worker'
import { vector } from '@electric-sql/pglite/vector'
worker({
async init() {
return new PGlite({
extensions: {
vector,
},
})
},
})
Extensions that only use the PGlite plugin interface, such as live queries, can be used on the main thread with PGliteWorker
to expose their functionality; this is done by providing a standard options object as a second argument to the PGliteWorker
constructor:
import { PGliteWorker } from '@electric-sql/pglite/worker'
import { live } from '@electric-sql/pglite/live'
const pg = new PGliteWorker(
new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
type: 'module',
}),
{
extensions: {
live,
},
},
)
PGliteWorker
also has a create
static method that resolves to a new instance when it is fully initiated. This also adds the correct types for any extensions to the PGliteWorker
instance:
import { PGliteWorker } from '@electric-sql/pglite/worker'
import { live } from '@electric-sql/pglite/live'
const pg = await PGliteWorker.create(
new Worker(new URL('./my-pglite-worker.js', import.meta.url), {
type: 'module',
}),
{
extensions: {
live,
},
},
)
// TypeScript is aware of the `pg.live` namespace:
pg.live.query(/* ... */)