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/fuzzy-dodos-work.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite': patch
---

Report a clear error when OPFS-AHP is used outside a Dedicated Web Worker.
2 changes: 1 addition & 1 deletion docs/docs/filesystems.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ The IndexedDB filesystem works at the file level, storing whole files (Postgres

## OPFS AHP FS

The OPFS AHP filesystem is built on top of the [Origin Private Filesystem](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system) in the browser and uses an "access handle pool". It is only available when PGlite is run in a Web Worker, this could be any worker you configure. We provide a [Multi Tab Worker](./multi-tab-worker.md) to aid in using PGlite from multiple tabs in the browser.
The OPFS AHP filesystem is built on top of the [Origin Private Filesystem](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system) in the browser and uses an "access handle pool". It is only available when PGlite is run in a Dedicated Web Worker. We provide a [Multi Tab Worker](./multi-tab-worker.md) to aid in using PGlite from multiple tabs in the browser.

To use the OPFS AHP FS you can use one of these methods:

Expand Down
19 changes: 19 additions & 0 deletions packages/pglite/src/fs/opfs-ahp-support.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
interface FileSystemFileHandleConstructor {
prototype: object
}

export function isOpfsAhpSupported(
fileSystemFileHandle:
| FileSystemFileHandleConstructor
| undefined = typeof FileSystemFileHandle === 'undefined'
? undefined
: FileSystemFileHandle,
): boolean {
return (
typeof (
fileSystemFileHandle?.prototype as {
createSyncAccessHandle?: unknown
}
)?.createSyncAccessHandle === 'function'
)
}
7 changes: 7 additions & 0 deletions packages/pglite/src/fs/opfs-ahp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BaseFilesystem, ERRNO_CODES, type FsStats } from './base.js'
import type { PostgresMod } from '../postgresMod.js'
import { PGlite } from '../pglite.js'
import { isOpfsAhpSupported } from './opfs-ahp-support.js'

export interface OpfsAhpOptions {
initialPoolSize?: number
Expand Down Expand Up @@ -129,6 +130,12 @@ export class OpfsAhpFS extends BaseFilesystem {
}

async #init() {
if (!isOpfsAhpSupported()) {
throw new Error(
'OPFS-AHP is only supported in a Dedicated Web Worker because FileSystemFileHandle.createSyncAccessHandle() is not available in this context.',
)
}

this.#opfsRootAh = await navigator.storage.getDirectory()
this.#rootAh = await this.#resolveOpfsDirectory(this.dataDir!, {
create: true,
Expand Down
42 changes: 42 additions & 0 deletions packages/pglite/tests/opfs-ahp.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it, vi } from 'vitest'

vi.mock('../src/fs/base.js', () => ({
BaseFilesystem: class {
protected dataDir?: string

constructor(dataDir?: string) {
this.dataDir = dataDir
}
},
ERRNO_CODES: {},
}))

vi.mock('../src/pglite.js', () => ({ PGlite: class {} }))

import { OpfsAhpFS } from '../src/fs/opfs-ahp.js'
import { isOpfsAhpSupported } from '../src/fs/opfs-ahp-support.js'

describe('isOpfsAhpSupported', () => {
it('rejects environments without the sync access handle API', () => {
expect(isOpfsAhpSupported(undefined)).toBe(false)
expect(isOpfsAhpSupported({ prototype: {} })).toBe(false)
})

it('accepts the API shape exposed in Dedicated Web Workers', () => {
expect(
isOpfsAhpSupported({
prototype: { createSyncAccessHandle: () => undefined },
}),
).toBe(true)
})
})

describe('OpfsAhpFS', () => {
it('explains that OPFS-AHP requires a Dedicated Web Worker', async () => {
const fs = new OpfsAhpFS('test')

await expect(fs.init(undefined as never, {})).rejects.toThrow(
'OPFS-AHP is only supported in a Dedicated Web Worker because FileSystemFileHandle.createSyncAccessHandle() is not available in this context.',
)
})
})
Loading