UNPKG

@pgxsinkit/pgwasm

Version:
139 lines (111 loc) • 8.09 kB
--- 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>.