---
name: opfs
description: >-
  Load when wiring @pgxsinkit/pgwasm/opfs (the OPFS-repacked store) into a browser worker, choosing a worker scope,
  choosing relaxed or strict
  durability, handling store-open failures, or deleting and recreating a store after a format identity
  change. Covers the createOpfsPgwasm factory as the only construction seam (it takes the Postgres build),
  dedicated-directory ownership, the constant four OPFS handles, worker requirements, extent-size identity,
  awaited pgwasm host syncs, strictSync(pg), close behavior, the supported browser-termination model, the
  stable error remedies, and the lower-level parts. Load before constructing, operating, or recovering a
  pgwasm database on an OPFS-repacked store.
metadata:
  type: task
  library: "@pgxsinkit/pgwasm"
  library_version: "0.5.2"
  source: https://pgxsinkit.github.io/packages/pgwasm/
---

# Operating a pgwasm database on an OPFS-repacked store

Use `createOpfsPgwasm` from `@pgxsinkit/pgwasm/opfs` and no other construction path. The factory takes the
Postgres build, retains the store, forces pgwasm onto its awaited sync path, performs a strict sync before
returning a successfully initialized database, and closes all four handles after failed initialization or
shutdown.

## Construct it in a capability-proven worker

Create one otherwise-empty OPFS directory per database and pass its handle to the factory with the build:

```ts
import { createOpfsPgwasm, strictSync } from "@pgxsinkit/pgwasm/opfs";
import { live } from "@pgxsinkit/pgwasm/live";
import { cBuild } from "@pgxsinkit/pgwasm-c";

const root = await navigator.storage.getDirectory();
const directory = await root.getDirectoryHandle("app-database", { create: true });
const pg = await createOpfsPgwasm({
  build: cBuild,
  directory,
  durability: "relaxed",
  extentSize: 64 * 1024,
  pgwasm: { extensions: { live } },
});
```

Require a successful `createSyncAccessHandle()` open in the executing scope; method presence is not
proof. Chromium and Firefox grant it in dedicated workers and deny it in SharedWorkers. Real macOS
and iOS Safari grant it in SharedWorkers (full boot/persist/reopen verified 2026-07-21). Do not run
the database on the window main thread. A store owns exactly four handles regardless of its virtual
file count.

The store accepts a directory handle and does not choose placement. For a cross-browser
pgxsinkit app, use `@pgxsinkit/client`: capability-driven placement is automatic (there is no placement
option), and a boot-time OPFS probe decides the engine's home — Safari runs the engine in the
SharedWorker; Chromium and Firefox elect a dedicated engine worker. Playwright WebKitGTK denies the
capability in both scopes and exercises the IndexedDB fallback; do not generalize that result to Safari.

`build` is required: the Postgres build the database runs on (e.g. `cBuild` from `@pgxsinkit/pgwasm-c`).
A data directory belongs to the build that created it, so reopening it with another build is refused
(`BuildMismatchError`, `DataFormatMismatchError`, `BuildMarkerUnreadableError`, all from `@pgxsinkit/pgwasm`);
these refusals are permanent — never retry them. The `pgwasm` option accepts every other `createPgwasm`
option, such as extensions. The store owns `build`, `dataDir`, `fs` and `relaxedDurability`, so `pgwasm`
excludes all four (the types forbid them).

The optional `onPhase` callback reports `"store-opened"` (handles acquired, the repacked filesystem open) and
then `"pgwasm-ready"` (the database's boot completed), once each and only on success — diagnosability only,
for attributing a create that never returns to a step. It carries no policy and must not throw.

## Choose durability once

- `durability: "relaxed"` (default): an ordinary awaited host sync asserts health and performs any due
  deferred repack without running the per-query strict sequence. After at least 4 MiB of accumulated
  arena writes it may perform an extra arena-only amortization flush. Termination may lose an unflushed
  suffix, but recovery keeps the longest valid metadata-log prefix and never crosses extent owners.
- `durability: "strict"`: every awaited host sync flushes arena data before metadata. Successful query
  completion is a strict durability boundary.

pgwasm itself is always configured to await the store's sync. A non-awaited sync proves construction
was bypassed, raises `DurabilityModeMismatchError`, and poisons the instance. Do not introduce another
durability option at a call site.

`strictSync(pg)` (from `/opfs`, the same pattern as `protocol(pg)`) stabilizes every preceding operation in
strict order on demand, under the database's exclusive lock. It throws `UnsupportedFeatureError` for a
database `createOpfsPgwasm` did not create.

Successful initialization, repack activation, and close from an open instance always use strict
ordering. Close from a poisoned instance attempts no persistence and still releases all handles.

A platform write the store could not complete (an arena write rejected before a single byte was
confirmed, or a failed metadata-log append) poisons the instance: that call and every later one throw
`StoreFailedError`, whose `code` is 29 (`EIO`), so Postgres sees an I/O error and a commit whose write
failed is never acknowledged. A write the platform accepted in part returns the short count and does not poison. Close and reopen; do not retry on the live instance.

## Reopen and recreate

`extentSize` is chosen only for a new store: 8 KiB–16 MiB, aligned to 8 KiB, default 64 KiB. The persisted
identity controls reopen. Omit the option or pass the same value; a different valid value raises
`ExtentSizeMismatchError` without changing the store.

`StoreRecreationRequiredError` means this build does not accept the directory's format identity. Close
all owners, delete the complete directory externally, and create fresh:

```ts
await pg.close();
await root.removeEntry("app-database", { recursive: true });
```

Never copy individual owned files into the fresh directory. `CorruptStoreError` is different: the
activated authority is invalid, so restore an external backup or recreate. The VFS fails closed and
does not select another apparent generation.

## Handle errors by class

- `FsError`: fix the caller operation; non-terminal.
- `StoreLimitError`: recover space or let repack run; some exhausted identities require recreation.
- `StoreOwnedError`: another live instance owns an exclusive handle; close it and retry.
- `UnexpectedStoreEntryError`: the directory is not dedicated and empty; choose a correct directory.
- `ExtentSizeMismatchError`: omit `extentSize` or use the stored value.
- `DurabilityModeMismatchError`: terminal factory-wiring error; close and rebuild through the factory.
- `StoreFailedError` (`code` 29, `EIO`): the live instance is poisoned; close/reopen and inspect `cause`.
- `StoreClosedError`: stop using the adapter.

The guaranteed model covers worker, tab, process, and browser termination; unflushed writes may be
absent, partial, or independently present; completed flushes remain stable. Power loss, media failure,
arbitrary external edits, and mysteriously missing activated files are outside the guarantee and fail
closed.

## Lower-level parts

`/opfs` also exports what the factory is built from, for a host that owns a store itself: the storage ports
(`OpfsRepackedPort`, `FileRepackedPort` on Bun, `MemoryRepackedPort` with fault injection), the engine-agnostic
core (`RepackedVfs` over any `RepackedPort`), the mounted filesystem (`MountedRepackedVfs`, `OpfsRepackedFS`),
a synchronous broker that lets one coordinator worker own a store while other threads reach it over a
`SharedArrayBuffer` channel (`RepackedSyncBroker`, `RepackedSyncClient`), and a WASI preview1 filesystem
adapter (`createWasiPreview1Fs`) routing a wasm engine's file calls to one store through that broker. Store-level
errors carry a string `storeCode`; wrapped errors retain `cause`.

Full prose: <https://pgxsinkit.github.io/packages/pgwasm/#the-opfs-repacked-store-pgxsinkitpgwasmopfs>.
