# API The sync backend works in every browser that has OPFS, but the browser only allows it inside a dedicated worker — `createOPFS()` / `createOPFSDedicated()` set that up for you. The async backend can run on the main thread or in a SharedWorker, but needs a modern browser — writes will not work in Safari before 26. ## Package entries | Import from | Worker | Use it when | | -------------------------- | ------------------------------------------ | --------------------------------------------------------------------- | | `opfs-worker` | Starts a dedicated worker | Default; fine if bundle size doesn’t matter or tree-shaking is set up | | `opfs-worker/sync` | Starts a dedicated worker | Same as the default, without async / shared code in the bundle | | `opfs-worker/async` | Runs directly in the current thread | Main thread or your own worker; Safari prior to 26 can’t write | | `opfs-worker/sharedworker` | Starts one SharedWorker shared by all tabs | One shared fs process for every tab; Safari prior to 26 can’t write | | `opfs-worker/pure` | None | Low-level classes to build your own custom worker | ## Facade Every helper returns an `OPFSFacade` — the same Node-like API in all modes. The facade hides the actual work: depending on which helper you call, it either creates a dedicated worker and talks to it, works with OPFS directly in the current thread, or connects to a SharedWorker. Each takes optional `[options](#options)`. | Function | From | Under the hood | | ------------------ | ------------------------------------------- | ------------------------------------------------ | | `createOPFS` | `opfs-worker` or `opfs-worker/sync` | Dedicated worker + `OPFSSync` | | `createOPFSAsync` | `opfs-worker` or `opfs-worker/async` | OPFS directly, `OPFSAsync` in the current thread | | `createOPFSShared` | `opfs-worker` or `opfs-worker/sharedworker` | SharedWorker + `OPFSAsync` | ```typescript import { createOPFS, createOPFSAsync, createOPFSShared } from 'opfs-worker'; const fs = createOPFS(); // or createOPFSAsync(), createOPFSShared({ url: '...' }) ``` ### File I/O Paths are `string` or `URL` everywhere below. | Method | Parameters | Returns | | -------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | | `readFile` | `path`, optional `encoding` or `{ encoding }` | `Promise` — auto by extension if omitted (`.txt` → string, `.bin` → bytes) | | `writeFile` | `path`, `data` (`string` / `Uint8Array` / `ArrayBuffer` / `Blob`), optional encoding | `Promise` | | `appendFile` | `path`, `data` (`string` / `Uint8Array` / `ArrayBuffer` / `Blob`), optional encoding | `Promise` | | `readText` | `path`, `encoding?` (default `'utf-8'`) | `Promise` | | `writeText` | `path`, `text`, `encoding?` (default `'utf-8'`) | `Promise` | | `appendText` | `path`, `text`, `encoding?` (default `'utf-8'`) | `Promise` | | `readBlob` | `path` | `Promise` — disk-backed, not copied into memory. See [streaming](../guides/streaming.md) | | `importStream` | `path`, stream / `Blob` / `File` / `FileSystemFileHandle`, optional `{ onProgress }` | `Promise` — bytes written. See [streaming](../guides/streaming.md) | | `importFiles` | pairs / `Map` / directory or file handles, optional `{ onProgress, prefix }` | `Promise` — see [streaming](../guides/streaming.md) | For Node compatibility, `fs.promises` is the same instance (`fs.promises === fs`). ### Directories & metadata | Method | Parameters | Returns | | ---------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- | | `mkdir` | `path`, optional `{ recursive }` (numeric mode accepted and ignored) | `Promise` | | `readDir` | `path` | `Promise` | | `stat` | `path` | `Promise` | | `exists` | `path` | `Promise` | | `realpath` | `path` | `Promise` | | `remove` | `path`, optional `{ recursive, force }` | `Promise` | | `rename` | `oldPath`, `newPath`, optional `{ overwrite }` | `Promise` | | `copy` | `source`, `destination`, optional `{ recursive, overwrite }` | `Promise` | | `clear` | `path?` (default `/`) | `Promise` — empties the directory, keeps the directory itself | | `index` | — | `Promise>` | ### Watch & lifecycle | Method | Parameters | Returns | | ------------ | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `watch` | `path`, optional `{ recursive, include, exclude }`, optional `listener` | `() => void` unsubscribe — Node-style; see [watching](../guides/watching.md) | | `unwatch` | `path` | `void` | | `setOptions` | `options` — see [Options](#options) / [hashing](../guides/hashing.md) | `Promise` | | `dispose` | — | `void` — tears down watches, backend, and worker | ### File descriptors Dedicated / `OPFSSync` only — async throws `ENOTSUP`. Details: [file descriptors](./file-descriptors.md). | Method | Parameters | Returns | | ----------- | -------------------------------------------------- | ------------------------------------------- | | `open` | `path`, optional `{ create, exclusive, truncate }` | `Promise` — file descriptor | | `read` | `fd`, `buffer`, `offset`, `length`, `position?` | `Promise<{ bytesRead, buffer }>` | | `write` | `fd`, `buffer`, `offset?`, `length?`, `position?` | `Promise` — bytes written | | `close` | `fd` | `Promise` | | `fstat` | `fd` | `Promise` | | `ftruncate` | `fd`, `size?` (default `0`) | `Promise` | | `fsync` | `fd` | `Promise` — best-effort flush in OPFS | ### Node aliases | Alias | Parameters | Returns | | --------- | --------------------------------------- | ----------------------------------------------- | | `unlink` | `path` | same as `remove(path)` | | `rm` | `path`, optional `{ recursive, force }` | same as `remove` | | `rmdir` | `path` | same as `remove(path)` | | `readdir` | `path` | same as `readDir` | | `lstat` | `path` | same as `stat` | | `chmod` | `path`, `mode` | `Promise` — no-op (no Unix modes in OPFS) | ### Backend access `fs.backend` is the raw bytes API the facade wraps — a Comlink proxy to the worker for dedicated / SharedWorker, or the in-process `OPFSAsync` instance for async. Same methods as the facade, but without encoding helpers (you pass / get `Uint8Array`). | Field | Type | Notes | | ------------ | --------------------------------------- | ---------------------------------------------------- | | `fs.backend` | [`OPFSApi`](../types.md#opfsapi) | bytes in / bytes out | | `fs.worker` | `Worker` / `SharedWorker` / `undefined` | set when a worker was created; `undefined` for async | ## Options Passed to any `createOPFS*` (and to `setOptions()` later). | Option | Default | What it does | | ------------------ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | `root` | `'/'` | Scope all paths under this OPFS subdirectory | | `namespace` | `'opfs-worker:${root}'` | Stamp on every [watch](../guides/watching.md) event | | `hashAlgorithm` | `'etag'` | File hash on `stat` / `index` / watch — `'etag'`, `'SHA-*'`, or `null`/`false` to disable. See [hashing](../guides/hashing.md) | | `maxFileSize` | `50MB` | Skip SHA- hashing above this size (`etag` ignores it) | | `broadcastChannel` | `'opfs-worker'` | Channel name, a `BroadcastChannel` instance, or `null` to disable | ```typescript const fs = createOPFS({ root: '/my-app', hashAlgorithm: 'SHA-256', maxFileSize: 10 * 1024 * 1024, }); ``` ### Dedicated worker only | Option | Type | Default | What it does | | -------- | ---------------- | -------------- | ---------------------------------------------------------------------------------- | | `url` | `string` / `URL` | inlined worker | Script URL for `opfs-worker/dedicated.worker.js` (or your own) instead of the blob | | `worker` | `Worker` | — | Pass an existing `Worker` (overrides `url`) | Leave these unset unless you need them — the default inlined worker is enough. ```typescript const fs = createOPFS({ root: '/my-app' }); // Escape hatches only: // createOPFS({ root: '/my-app', url: workerUrl }) // CSP without blob: // createOPFS({ root: '/my-app', worker: existingWorker }) // bring your own Worker ``` ### SharedWorker only | Option | Type | Default | What it does | | -------- | ---------------- | --------------- | ----------------------------------------------------------------------------------------------------- | | `url` | `string` / `URL` | next to package | Script URL for `opfs-worker/shared.worker.js` — with Vite: `import url from '…/shared.worker.js?url'` | | `worker` | `SharedWorker` | — | Pass an existing `SharedWorker` (overrides `url`) | | `name` | `string` | `'opfs-worker'` | Name **prefix** — real SharedWorker name is `` `${name}:${root}` `` so different roots stay isolated | ```typescript import workerUrl from 'opfs-worker/shared.worker.js?url'; // Vite import { createOPFSShared } from 'opfs-worker/sharedworker'; const fs = createOPFSShared({ root: '/my-app', url: workerUrl, // name: 'opfs-worker', // optional prefix → worker name becomes opfs-worker:/my-app }); ``` ## Ready-made worker files These files already contain a backend (`OPFSSync` or `OPFSAsync`) wrapped in Comlink expose. | Export | Inside | Start with | | --------------------------------- | --------------------- | -------------------------------------------------------- | | `opfs-worker/dedicated.worker.js` | Comlink + `OPFSSync` | `new Worker(url, { type: 'module' })` or `{ url }` | | `opfs-worker/shared.worker.js` | Comlink + `OPFSAsync` | `new SharedWorker(url, { type: 'module' })` or `{ url }` | ### Use the facade with a worker URL This is required for SharedWorker. For dedicated workers, use it only when the default inlined worker is blocked by CSP or you want to host the script yourself. ```typescript import workerUrl from 'opfs-worker/dedicated.worker.js?url'; const fs = createOPFS({ root: '/my-app', url: workerUrl }); ``` You can also create the `Worker` yourself and pass it to the facade. The facade still handles Comlink, options, encoding helpers, buffer transfers, and cleanup. ```typescript const worker = new Worker(workerUrl, { type: 'module' }); const fs = createOPFS({ root: '/my-app', worker }); ``` ### Use the worker without the facade Wrap it with Comlink and configure the backend manually. This exposes the bytes API directly: no encoding helpers, automatic FD buffer transfers, or automatic worker termination. ```typescript import { wrap } from 'comlink'; import type { OPFSApi } from 'opfs-worker'; import workerUrl from 'opfs-worker/dedicated.worker.js?url'; const worker = new Worker(workerUrl, { type: 'module' }); const backend = wrap(worker); await backend.setOptions({ root: '/my-app' }); await backend.writeFile('/hello.txt', new TextEncoder().encode('hello')); await backend.dispose(); worker.terminate(); ``` Guides: [Dedicated worker](../guides/dedicated.md), [SharedWorker](../guides/sharedworker.md). ## Trade-offs | | Dedicated (`OPFSSync`) | Async (`OPFSAsync`) | | ----------------------------------- | ------------------------------------------------ | ---------------------------- | | File descriptors | yes | throw `ENOTSUP` | | Browser support | every browser that has OPFS | Safari before 26 can’t write | | SharedWorker | no | yes | | One instance for all tabs | no | yes, with `createOPFSShared` | | Bundle | ~80 KB inlined worker, or ready-made worker file | small via `/async` | | Works under strict CSP (no `blob:`) | pass a worker `url` instead of the inlined blob | n/a |