Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-initdb-fs-option-leak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': patch
---

Fix `PGlite.create({ fs })` on a fresh database calling the provided filesystem's `init()` twice: the inner initdb instance no longer inherits the user-provided `fs` and always runs on its own scratch filesystem. Previously any VFS holding exclusive resources (e.g. OPFS sync access handles) failed with a contention error on first create.
5 changes: 5 additions & 0 deletions packages/pglite/src/pglite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,11 @@ export class PGlite
pgInitDbOpts.dataDir = undefined
pgInitDbOpts.extensions = undefined
pgInitDbOpts.loadDataDir = undefined
// The initdb instance must run on its own scratch filesystem: a
// user-provided `fs` must not leak into it, or its second `init()`
// collides with resources the outer instance already holds (e.g. an
// OPFS VFS's sync access handles, which are exclusive per file).
pgInitDbOpts.fs = undefined
const pg_initDb = await PGlite.create(pgInitDbOpts)

// Initialize the database
Expand Down
31 changes: 31 additions & 0 deletions packages/pglite/tests/initdb-fs-option.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describe, it, expect } from 'vitest'
import { PGlite, MemoryFS } from '../dist/index.js'

// Regression test: creating a fresh database with a user-provided `fs` option
// boots an inner PGlite instance to run initdb. That inner instance must run
// on its own scratch filesystem — if the user's `fs` leaks into it, the
// filesystem's `init()` runs twice against the same backing resources, which
// breaks any VFS holding exclusive resources (e.g. OPFS sync access handles).
describe('initdb with user-provided fs option', () => {
it('does not re-init the provided fs for the inner initdb instance', async () => {
class CountingFS extends MemoryFS {
initCount = 0
async init(...args: Parameters<MemoryFS['init']>) {
this.initCount++
return super.init(...args)
}
}

const fs = new CountingFS()
const pg = await PGlite.create({ fs })

expect(fs.initCount).toBe(1)

// The store initialized and works end-to-end on the provided fs.
await pg.exec('CREATE TABLE t (id int)')
await pg.exec('INSERT INTO t VALUES (1)')
const res = await pg.query<{ id: number }>('SELECT id FROM t')
expect(res.rows).toEqual([{ id: 1 }])
await pg.close()
})
})