npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@componentor/fs

v3.0.20

Published

High-performance OPFS-based Node.js fs polyfill with true sync API, VFS binary format, and bidirectional OPFS mirroring

Readme

@componentor/fs

High-performance OPFS-based Node.js fs polyfill for the browser

A virtual filesystem powered by a custom binary format (VFS), SharedArrayBuffer + Atomics for true synchronous APIs, multi-tab coordination via Web Locks, and bidirectional OPFS mirroring.

import { VFSFileSystem } from '@componentor/fs';

const fs = new VFSFileSystem();

// Sync API (requires crossOriginIsolated — blocks until ready on first call)
fs.writeFileSync('/hello.txt', 'Hello World!');
const data = fs.readFileSync('/hello.txt', 'utf8');

// Async API (always available)
await fs.promises.writeFile('/async.txt', 'Async data');
const content = await fs.promises.readFile('/async.txt', 'utf8');

Features

  • True Sync APIreadFileSync, writeFileSync, etc. via SharedArrayBuffer + Atomics
  • Async APIpromises.readFile, promises.writeFile — works without COOP/COEP
  • VFS Binary Format — All data in a single .vfs.bin file for maximum throughput
  • OPFS Sync — Bidirectional mirror to real OPFS files (enabled by default)
  • Multi-tab Safe — Leader/follower architecture with automatic failover via navigator.locks
  • FileSystemObserver — External OPFS changes synced back to VFS automatically (Chrome 129+)
  • isomorphic-git Ready — Full compatibility with git operations
  • Zero Config — Workers inlined at build time, no external worker files needed
  • TypeScript First — Complete type definitions included

Installation

npm install @componentor/fs

Quick Start

import { VFSFileSystem } from '@componentor/fs';

const fs = new VFSFileSystem({ root: '/my-app' });

// Option 1: Sync API (blocks on first call until VFS is ready)
fs.mkdirSync('/my-app/src', { recursive: true });
fs.writeFileSync('/my-app/src/index.js', 'console.log("Hello!");');
const code = fs.readFileSync('/my-app/src/index.js', 'utf8');

// Option 2: Async init (non-blocking)
await fs.init(); // wait for VFS to be ready
const files = await fs.promises.readdir('/my-app/src');
const stats = await fs.promises.stat('/my-app/src/index.js');

Convenience Helpers

import { createFS, getDefaultFS, init } from '@componentor/fs';

// Create with config
const fs = createFS({ root: '/repo', debug: true });

// Lazy singleton (created on first access)
const defaultFs = getDefaultFS();

// Async init helper
await init(); // initializes the default singleton

Configuration

const fs = new VFSFileSystem({
  root: '/',              // OPFS root directory (default: '/')
  mode: 'hybrid',        // 'hybrid' | 'vfs' | 'opfs' (default: 'hybrid')
  opfsSyncRoot: undefined, // Custom OPFS root for mirroring (default: same as root)
  uid: 0,                 // User ID for file ownership (default: 0)
  gid: 0,                 // Group ID for file ownership (default: 0)
  umask: 0o022,           // File creation mask (default: 0o022)
  strictPermissions: false, // Enforce Unix permissions (default: false)
  sabSize: 4194304,       // SharedArrayBuffer size in bytes (default: 4MB)
  debug: false,           // Enable debug logging (default: false)
  swUrl: undefined,       // URL of the service worker script (default: auto-resolved)
  swScope: undefined,     // Custom service worker scope (default: auto-scoped per root)
  limits: {               // Upper bounds for VFS validation (prevents corrupt data from causing OOM)
    maxInodes: 4_000_000,   // Max inode count (default: 4M)
    maxBlocks: 4_000_000,   // Max data blocks (default: 4M)
    maxPathTable: 256 * 1024 * 1024, // Max path table bytes (default: 256MB)
    maxVFSSize: 100 * 1024 * 1024 * 1024, // Max .vfs.bin size (default: 100GB)
    maxPayload: 2 * 1024 * 1024 * 1024,   // Max single SAB payload (default: 2GB)
  },
});

Filesystem Modes

The mode option controls how the filesystem stores data:

| Mode | Storage | OPFS Sync | Speed | Resilience | |------|---------|-----------|-------|------------| | hybrid (default) | VFS binary + OPFS mirror | Bidirectional | Fast | High | | vfs | VFS binary only | None | Fastest | Medium | | opfs | Real OPFS files only | N/A | Slower | Highest |

// Hybrid mode (default) — best of both worlds
const fs = new VFSFileSystem({ mode: 'hybrid' });
fs.writeFileSync('/file.txt', 'data');
// → stored in .vfs.bin AND mirrored to real OPFS files

// VFS-only mode — maximum performance, no OPFS mirroring
const fastFs = new VFSFileSystem({ mode: 'vfs' });

// OPFS-only mode — no VFS binary, operates directly on OPFS files
const safeFs = new VFSFileSystem({ mode: 'opfs' });

Hybrid mode mirrors all VFS mutations to real OPFS files in the background:

  • VFS → OPFS: Every write, delete, mkdir, rename is replicated after the sync operation completes (zero performance impact on the hot path)
  • OPFS → VFS: A FileSystemObserver watches for external changes and syncs them back (Chrome 129+)

This allows external tools (browser DevTools, OPFS extensions) to see and modify files while VFS handles all the fast read/write operations internally.

Corruption Fallback

In hybrid mode, if VFS corruption is detected during initialization, the filesystem automatically falls back to opfs mode. The init() call rejects with an error describing the corruption, but all filesystem operations continue working via OPFS:

const fs = new VFSFileSystem(); // hybrid mode

try {
  await fs.init();
} catch (err) {
  // VFS was corrupt — system is now running in OPFS mode
  console.warn(err.message); // "Falling back to OPFS mode: <reason>"
  console.log(fs.mode);      // 'opfs'
}

// Filesystem still works — reads/writes go through OPFS
fs.writeFileSync('/file.txt', 'still works!');

Runtime Mode Switching

Use setMode() to switch modes at runtime. This is useful for IDE workflows where you want to recover from corruption:

// Corruption detected, currently in OPFS fallback mode
console.log(fs.mode); // 'opfs'

// Repair the VFS binary
await repairVFS('/my-app');

// Switch back to hybrid mode
await fs.setMode('hybrid');
console.log(fs.mode); // 'hybrid'

setMode() terminates internal workers, allocates fresh shared memory, and reinitializes the filesystem in the requested mode.

Service Worker Setup (Multi-Tab)

Multi-tab coordination requires a service worker that acts as a MessagePort broker between tabs. The built service worker is shipped at dist/workers/service.worker.js. Unlike regular workers (which are resolved by the bundler), service workers must be served as a real file at a public URL.

Most bundlers (Vite, webpack) handle new URL('./workers/service.worker.js', import.meta.url) automatically, but if the default resolution doesn't work in your setup, use the swUrl option:

const fs = new VFSFileSystem({
  swUrl: '/vfs-service-worker.js', // your public URL
});

Vite example — copy the file to public/:

cp node_modules/@componentor/fs/dist/workers/service.worker.js public/vfs-service-worker.js
const fs = new VFSFileSystem({ swUrl: '/vfs-service-worker.js' });

If you only use a single tab, the service worker is not needed — the tab always runs as the leader.

COOP/COEP Headers

To enable the sync API, your page must be crossOriginIsolated. Add these headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Without these headers, only the async (promises) API is available.

Vite

// vite.config.ts
export default defineConfig({
  server: {
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
});

Express

app.use((req, res, next) => {
  res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
  res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
  next();
});

Vercel

{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cross-Origin-Opener-Policy", "value": "same-origin" },
        { "key": "Cross-Origin-Embedder-Policy", "value": "require-corp" }
      ]
    }
  ]
}

Runtime Check

if (crossOriginIsolated) {
  // Sync + async APIs available
  fs.writeFileSync('/fast.txt', 'blazing fast');
} else {
  // Async API only
  await fs.promises.writeFile('/fast.txt', 'still fast');
}

Benchmarks

Tested against LightningFS (IndexedDB-based) in Chrome with crossOriginIsolated enabled:

| Operation | LightningFS | VFS Sync | VFS Promises | Winner | |-----------|------------|----------|-------------|--------| | Write 100 x 1KB | 46ms | 12ms | 23ms | VFS 4x | | Write 100 x 4KB | 36ms | 13ms | 22ms | VFS 2.8x | | Read 100 x 1KB | 19ms | 2ms | 14ms | VFS 9x | | Read 100 x 4KB | 62ms | 2ms | 13ms | VFS 28x | | Large 10 x 1MB | 11ms | 10ms | 17ms | VFS 1.1x | | Batch Write 500 x 256B | 138ms | 50ms | 75ms | VFS 2.8x | | Batch Read 500 x 256B | 73ms | 7ms | 91ms | VFS 10x |

Key takeaways:

  • Reads are 9-28x faster — VFS binary format eliminates IndexedDB overhead
  • Writes are 2.8-4x faster — Single binary file vs individual OPFS/IDB entries
  • Batch operations are 2.8-10x faster — VFS excels at many small operations
  • VFS Sync is the fastest path (SharedArrayBuffer + Atomics, zero async overhead)

Run benchmarks yourself:

npm run benchmark:open

API Reference

Sync API (requires crossOriginIsolated)

// Read/Write
fs.readFileSync(path, options?): Uint8Array | string
fs.writeFileSync(path, data, options?): void
fs.appendFileSync(path, data): void

// Directories
fs.mkdirSync(path, options?): void
fs.rmdirSync(path, options?): void
fs.rmSync(path, options?): void
fs.readdirSync(path, options?): string[] | Dirent[]

// File Operations
fs.unlinkSync(path): void
fs.renameSync(oldPath, newPath): void
fs.copyFileSync(src, dest, mode?): void
fs.truncateSync(path, len?): void
fs.symlinkSync(target, path): void
fs.readlinkSync(path): string
fs.linkSync(existingPath, newPath): void

// Info
fs.statSync(path): Stats
fs.lstatSync(path): Stats
fs.existsSync(path): boolean
fs.accessSync(path, mode?): void
fs.realpathSync(path): string

// Metadata
fs.chmodSync(path, mode): void
fs.chownSync(path, uid, gid): void
fs.utimesSync(path, atime, mtime): void

// File Descriptors
fs.openSync(path, flags?, mode?): number
fs.closeSync(fd): void
fs.readSync(fd, buffer, offset?, length?, position?): number
fs.writeSync(fd, buffer, offset?, length?, position?): number
fs.fstatSync(fd): Stats
fs.ftruncateSync(fd, len?): void
fs.fdatasyncSync(fd): void

// Temp / Flush
fs.mkdtempSync(prefix): string
fs.flushSync(): void

Async API (always available)

// Read/Write
fs.promises.readFile(path, options?): Promise<Uint8Array | string>
fs.promises.writeFile(path, data, options?): Promise<void>
fs.promises.appendFile(path, data): Promise<void>

// Directories
fs.promises.mkdir(path, options?): Promise<void>
fs.promises.rmdir(path, options?): Promise<void>
fs.promises.rm(path, options?): Promise<void>
fs.promises.readdir(path, options?): Promise<string[] | Dirent[]>

// File Operations
fs.promises.unlink(path): Promise<void>
fs.promises.rename(oldPath, newPath): Promise<void>
fs.promises.copyFile(src, dest, mode?): Promise<void>
fs.promises.truncate(path, len?): Promise<void>
fs.promises.symlink(target, path): Promise<void>
fs.promises.readlink(path): Promise<string>
fs.promises.link(existingPath, newPath): Promise<void>

// Info
fs.promises.stat(path): Promise<Stats>
fs.promises.lstat(path): Promise<Stats>
fs.promises.exists(path): Promise<boolean>
fs.promises.access(path, mode?): Promise<void>
fs.promises.realpath(path): Promise<string>

// Metadata
fs.promises.chmod(path, mode): Promise<void>
fs.promises.chown(path, uid, gid): Promise<void>
fs.promises.utimes(path, atime, mtime): Promise<void>

// Advanced
fs.promises.open(path, flags?, mode?): Promise<FileHandle>
fs.promises.opendir(path): Promise<Dir>
fs.promises.mkdtemp(prefix): Promise<string>

// Flush
fs.promises.flush(): Promise<void>

Streams API

// Readable stream (Web Streams API)
const stream = fs.createReadStream('/large-file.bin', {
  start: 0,           // byte offset to start
  end: 1024,          // byte offset to stop
  highWaterMark: 64 * 1024, // chunk size (default: 64KB)
});
for await (const chunk of stream) {
  console.log('Read chunk:', chunk.length, 'bytes');
}

// Writable stream
const writable = fs.createWriteStream('/output.bin');
const writer = writable.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
await writer.close();

Instance Methods

// Get the current filesystem mode
fs.mode: 'hybrid' | 'vfs' | 'opfs'

// Switch mode at runtime (terminates workers, reinitializes)
await fs.setMode('hybrid' | 'vfs' | 'opfs'): Promise<void>

// Non-blocking async init (waits for VFS to be ready)
await fs.init(): Promise<void>

Watch API

// Watch for changes (supports recursive + AbortSignal)
const ac = new AbortController();
const watcher = fs.watch('/dir', { recursive: true, signal: ac.signal }, (eventType, filename) => {
  console.log(eventType, filename); // 'rename' 'newfile.txt' or 'change' 'file.txt'
});
watcher.close(); // or ac.abort()

// Watch specific file with stat polling
fs.watchFile('/file.txt', { interval: 1000 }, (curr, prev) => {
  console.log('File changed:', curr.mtimeMs !== prev.mtimeMs);
});
fs.unwatchFile('/file.txt');

// Async iterable (promises API)
for await (const event of fs.promises.watch('/dir', { recursive: true })) {
  console.log(event.eventType, event.filename);
}

Path Utilities

import { path } from '@componentor/fs';

path.join('/foo', 'bar', 'baz')       // '/foo/bar/baz'
path.resolve('foo', 'bar')            // '/foo/bar'
path.dirname('/foo/bar/baz.txt')      // '/foo/bar'
path.basename('/foo/bar/baz.txt')     // 'baz.txt'
path.extname('/foo/bar/baz.txt')      // '.txt'
path.normalize('/foo//bar/../baz')    // '/foo/baz'
path.isAbsolute('/foo')               // true
path.relative('/foo/bar', '/foo/baz') // '../baz'
path.parse('/foo/bar/baz.txt')        // { root, dir, base, ext, name }
path.format({ dir: '/foo', name: 'bar', ext: '.txt' }) // '/foo/bar.txt'

Constants

import { constants } from '@componentor/fs';

constants.F_OK  // 0 - File exists
constants.R_OK  // 4 - File is readable
constants.W_OK  // 2 - File is writable
constants.X_OK  // 1 - File is executable

constants.COPYFILE_EXCL  // 1 - Fail if dest exists

constants.O_RDONLY   // 0
constants.O_WRONLY   // 1
constants.O_RDWR     // 2
constants.O_CREAT    // 64
constants.O_EXCL     // 128
constants.O_TRUNC    // 512
constants.O_APPEND   // 1024

Maintenance Helpers

Standalone utilities for VFS maintenance, recovery, and migration. Must be called from a Worker context (sync access handle requirement). Close any running VFSFileSystem instance first.

import { unpackToOPFS, loadFromOPFS, repairVFS } from '@componentor/fs';

// Export VFS contents to real OPFS files (clears existing OPFS files first)
const { files, directories } = await unpackToOPFS('/my-app');

// Rebuild VFS from real OPFS files (deletes .vfs.bin, creates fresh VFS)
const { files, directories } = await loadFromOPFS('/my-app');

// Attempt to recover files from a corrupt VFS binary
const { recovered, lost, entries } = await repairVFS('/my-app');
console.log(`Recovered ${recovered} entries, lost ${lost}`);
for (const entry of entries) {
  console.log(`  ${entry.type} ${entry.path} (${entry.size} bytes)`);
}

| Function | Description | |----------|-------------| | unpackToOPFS(root?) | Read all files from VFS, write to real OPFS paths | | loadFromOPFS(root?) | Read all OPFS files, create fresh VFS with their contents | | repairVFS(root?) | Scan corrupt .vfs.bin for recoverable inodes, rebuild fresh VFS |

isomorphic-git Integration

import { VFSFileSystem } from '@componentor/fs';
import git from 'isomorphic-git';
import http from 'isomorphic-git/http/web';

const fs = new VFSFileSystem({ root: '/repo' });

// Clone a repository
await git.clone({
  fs,
  http,
  dir: '/repo',
  url: 'https://github.com/user/repo',
  corsProxy: 'https://cors.isomorphic-git.org',
});

// Check status
const status = await git.statusMatrix({ fs, dir: '/repo' });

// Stage and commit
await git.add({ fs, dir: '/repo', filepath: '.' });
await git.commit({
  fs,
  dir: '/repo',
  message: 'Initial commit',
  author: { name: 'User', email: '[email protected]' },
});

Architecture

┌──────────────────────────────────────────────────────────────────┐
│                         Main Thread                              │
│  ┌──────────────┐  ┌──────────────┐  ┌────────────────────────┐  │
│  │   Sync API   │  │  Async API   │  │    Path / Constants    │  │
│  │ readFileSync │  │  promises.   │  │ join, dirname, etc.    │  │
│  │writeFileSync │  │  readFile    │  └────────────────────────┘  │
│  └──────┬───────┘  └──────┬───────┘                              │
│         │                 │                                      │
│   SAB + Atomics     postMessage                                  │
└─────────┼─────────────────┼──────────────────────────────────────┘
          │                 │
          ▼                 ▼
┌──────────────────────────────────────────────────────────────────┐
│               sync-relay Worker (Leader)                         │
│  ┌────────────────────────────────────────────────────────────┐  │
│  │                     VFS Engine                             │  │
│  │  ┌──────────────────┐  ┌─────────────┐  ┌──────────────┐   │  │
│  │  │  VFS Binary File │  │  Inode/Path │  │  Block Data  │   │  │
│  │  │  (.vfs.bin OPFS) │  │    Table    │  │   Region     │   │  │
│  │  └──────────────────┘  └─────────────┘  └──────────────┘   │  │
│  └────────────────────────────────────────────────────────────┘  │
│                            │                                     │
│                    notifyOPFSSync()                              │
│                     (fire & forget)                              │
└────────────────────────────┼─────────────────────────────────────┘
                             │
                             ▼
┌──────────────────────────────────────────────────────────────────┐
│                    opfs-sync Worker                              │
│  ┌────────────────────┐  ┌────────────────────────────────────┐  │
│  │  VFS → OPFS Mirror │  │  FileSystemObserver (OPFS → VFS)   │  │
│  │  (queue + echo     │  │  External changes detected and     │  │
│  │   suppression)     │  │  synced back to VFS engine         │  │
│  └────────────────────┘  └────────────────────────────────────┘  │
└──────────────────────────────────────────────────────────────────┘

Multi-tab (via Service Worker + navigator.locks):
  Tab 1 (Leader) ←→ Service Worker ←→ Tab 2 (Follower)
  Tab 1 holds VFS engine, Tab 2 forwards requests via MessagePort
  If Tab 1 dies, Tab 2 auto-promotes to leader

Browser Support

| Browser | Sync API | Async API | |---------|----------|-----------| | Chrome 102+ | Yes | Yes | | Edge 102+ | Yes | Yes | | Firefox 111+ | Yes* | Yes | | Safari 15.2+ | No** | Yes | | Opera 88+ | Yes | Yes |

* Firefox requires dom.workers.modules.enabled flag ** Safari doesn't support SharedArrayBuffer in the required context

Troubleshooting

"SharedArrayBuffer is not defined"

Your page is not crossOriginIsolated. Add COOP/COEP headers (see above). The async API still works without them.

"Sync API requires crossOriginIsolated"

Same issue — sync methods (readFileSync, etc.) need SharedArrayBuffer. Use fs.promises.* as a fallback.

"Atomics.wait cannot be called in this context"

Atomics.wait only works in Workers. The library handles this internally — if you see this error, you're likely calling sync methods from the main thread without proper COOP/COEP headers.

Files not visible in OPFS DevTools

Make sure opfsSync is enabled (it's true by default). Files are mirrored to OPFS in the background after each VFS operation. Check DevTools > Application > Storage > OPFS.

External OPFS changes not detected

FileSystemObserver requires Chrome 129+. The VFS instance must be running (observer is set up during init). Changes to files outside the configured root directory won't be detected.

Changelog

v3.0.19-v3.0.20 (2026)

Features:

  • Add swUrl config option to specify a custom service worker URL for multi-tab support in bundled environments where the default auto-resolved URL doesn't work
  • Remove type: 'module' from service worker registration (built output is plain script, not ESM)

v3.0.18 (2026)

Features:

  • Configurable VFS limits via limits option: maxInodes, maxBlocks, maxPathTable, maxVFSSize, maxPayload

Fixes:

  • Pre-validate superblock before engine.init() to prevent hangs from corrupt values causing huge allocations
  • Add upper bounds in mount(): max 4M inodes, 4M blocks, 256MB path table, 100GB total VFS size
  • Ensure all mount errors are prefixed with Corrupt VFS: for consistent corruption fallback
  • Cap readPayload() at 2GB (configurable) and validate each chunk length in the multi-chunk loop to prevent OOM/infinite loops from corrupt SAB data
  • Cap MemoryHandle.grow() at 4GB to prevent OOM from corrupt VFS offsets on main-thread fallback

v3.0.17 (2026)

Features:

  • Auto-populate VFS from existing OPFS files when .vfs.bin doesn't exist — seamless transition from OPFS mode back to hybrid mode without manual loadFromOPFS() call

v3.0.16 (2026)

Fixes:

  • Add 10s timeout to main-thread spin-wait — prevents infinite busy loop if SharedWorker is dead or unresponsive

v3.0.15 (2026)

Fixes:

  • Add bounds validation to decodeRequest — rejects truncated SAB payloads instead of reading out-of-bounds
  • Wrap decodeRequest in try/catch in both VFS and OPFS leader loop handlers — corrupt buffers return status: -1 instead of crashing the leader loop
  • Guard readPayload against zero/negative/overflow chunk lengths from stale SAB data

v3.0.14 (2026)

Fixes:

  • Fix PATH_USED not persisted after write() without flush flag — commitPending() now runs unconditionally after every write, preventing "path out of bounds" corruption on reload
  • Repair inode scanner uses the full allocated path table region instead of PATH_USED from the superblock — recovers files even when the superblock counter was stale

v3.0.13 (2026)

Fixes:

  • Revert .d.ts extension override in outExtensiondts: true handles it correctly

v3.0.12 (2026)

Fixes:

  • Fix .d.ts output extension mapping so declaration files resolve correctly

v3.0.11 (2026)

Fixes:

  • Emit TypeScript declaration files (dts: true in tsup config)

v3.0.10 (2026)

New: Three filesystem modes (hybrid, vfs, opfs)

  • mode: 'hybrid' (default) — VFS binary + bidirectional OPFS sync
  • mode: 'vfs' — VFS binary only, no OPFS mirroring (fastest)
  • mode: 'opfs' — Pure OPFS files, no VFS binary (most resilient)
  • New OPFSEngine implements all fs operations directly on OPFS files

Automatic corruption fallback

  • Hybrid mode auto-falls back to OPFS mode on VFS corruption
  • await fs.init() rejects with descriptive error while system works in OPFS mode
  • fs.mode getter reflects current mode (changes to 'opfs' on fallback)

Runtime mode switching

  • await fs.setMode('hybrid' | 'vfs' | 'opfs') for switching modes at runtime
  • IDE workflow: corruption → OPFS fallback → repair → setMode('hybrid')

Corruption detection improvements

  • rebuildIndex() validates every inode: type, path bounds, data block range, path format
  • Fixed format() not persisting pathTableUsed after root inode creation

Repair safety

  • Dedicated repair worker with createSyncAccessHandle — no RAM bloat
  • Original .vfs.bin is never deleted until replacement is verified via re-mount
  • Copy-then-delete swap: crash mid-copy leaves .vfs.bin.tmp intact for retry
  • loadFromOPFS builds in temp file first — original untouched until verified
  • Strict UTF-8 decoding for recovered paths and symlink targets (rejects invalid sequences)
  • contentLost flag on repair entries distinguishes empty files from files with lost data
  • Repair aborts after 5 critical mkdir failures (fail-fast threshold)
  • Orphaned .vfs.bin.tmp files cleaned up automatically on repair entry

v3.0.9 (2026)

Improvements:

  • unpackToOPFS, loadFromOPFS, and repairVFS now accept an optional fs parameter (a running VFSFileSystem instance) so they work from any tab — leader or follower — without stopping the VFS
  • When fs is not provided, falls back to direct .vfs.bin access via VFSEngine (requires VFS to be stopped or a Worker context)
  • repairVFS with a running instance uses OPFS as source of truth: rebuilds VFS from OPFS, then syncs back for full consistency

v3.0.8 (2026)

Improvements:

  • Add VFS helper functions: unpackToOPFS, loadFromOPFS, and repairVFS for VFS maintenance, migration, and recovery
  • Helpers work in both Worker (sync access handle) and main thread (in-memory buffer + async writable) contexts
  • Remove redundant I/O call in unpackToOPFS directory creation

v3.0.7 (2026)

Fixes:

  • Fix fs.watch() path matching for root / watchers — watching / now correctly matches all child paths instead of missing them due to an off-by-one boundary check

v3.0.6 (2026)

Performance:

  • Bulk-read inode + path tables during mount — 2 I/O calls instead of 10,000+, dramatically faster initialization for large VFS files
  • All active inodes pre-populated in cache on mount (no cold-read penalty for first operations)

Fixes:

  • .vfs.bin now auto-shrinks: trailing free blocks are trimmed on every commit, reclaiming disk space when files are deleted
  • Minimum of 1024 data blocks (4MB) retained to avoid excessive re-growth on small create/delete cycles

v3.0.5 (2026)

Fixes:

  • Scope the internal service worker by default so it won't collide with the host application's own service worker
  • Remove unnecessary clients.claim() from the service worker — it only acts as a MessagePort broker and never needs to control pages
  • Namespace leader lock, BroadcastChannel, and SW scope by root so multiple VFSFileSystem instances with different roots don't collide
  • Add swScope config option for custom service worker scope override
  • Singleton registry: multiple new VFSFileSystem() calls with the same root return the same instance (no duplicate workers)
  • Namespace vfs-watch BroadcastChannel by root so watch events don't leak between different roots

v3.0.4 (2026)

Features:

  • Add unpackToOPFS(root?) — export all VFS contents to real OPFS files
  • Add loadFromOPFS(root?) — rebuild VFS from real OPFS files (deletes and recreates .vfs.bin)
  • Add repairVFS(root?) — scan corrupt VFS binary for recoverable inodes and rebuild a clean VFS
  • Add VFSEngine.exportAll() for extracting all files/dirs/symlinks with their data

Bug Fixes:

  • VFS corruption detection on init — validates magic, version, block size, inode count, section offsets, file size, and root directory existence
  • Release sync access handle on init failure (previously leaked, blocking re-acquisition)

v3.0.3 (2026)

Features:

  • Implement fs.watch(), fs.watchFile(), fs.unwatchFile(), and promises.watch() as Node.js-compatible polyfills
  • Watch events propagate across all tabs via BroadcastChannel
  • fs.watch() supports recursive option and AbortSignal for cleanup
  • fs.watchFile() supports stat-based polling with configurable interval (default 5007ms per Node.js)
  • promises.watch() returns an async iterable of watch events

Internal:

  • Leader broadcasts { eventType, path } on every successful VFS mutation (no new opcodes or protocol changes)
  • Mutation tracking now runs unconditionally (previously gated on opfsSync)

v3.0.2 (2026)

Bug Fixes:

  • Fix symlink resolution when resolved target path contains intermediate symlinks — resolvePath now falls back to component-by-component resolution instead of failing on direct lookup
  • Add ELOOP depth tracking to resolvePathComponents to prevent infinite recursion on circular symlinks
  • Mirror symlinks to OPFS as regular files (OPFS has no symlink concept) — reads through the symlink and writes the target's content

v3.0.1 (2026)

Bug Fixes:

  • Fix empty files (e.g. .gitkeep) not being mirrored to OPFS — both the sync-relay (skipped sending empty data) and opfs-sync worker (skipped writing 0-byte files) now handle empty files correctly

Benchmark:

  • Add memfs (in-memory) to the benchmark suite for comparison

v3.0.0 (2026)

Complete architecture rewrite — VFS binary format with SharedArrayBuffer.

New Architecture:

  • VFS binary format — all data stored in a single .vfs.bin file (Superblock → Inode Table → Path Table → Bitmap → Data Region)
  • SharedArrayBuffer + Atomics for true zero-overhead synchronous operations
  • Multi-tab leader/follower architecture with automatic failover via navigator.locks + Service Worker
  • Bidirectional OPFS sync — VFS mutations mirrored to real OPFS files, external changes synced back via FileSystemObserver
  • Workers inlined as blob URLs at build time (zero config, no external worker files)
  • Echo suppression for OPFS sync (prevents infinite sync loops)

Performance:

  • 9-28x faster reads vs LightningFS
  • 2.8-4x faster writes vs LightningFS
  • 2.8-10x faster batch operations vs LightningFS
  • Fire-and-forget OPFS sync — zero impact on hot path

Breaking Changes:

  • New API: new VFSFileSystem(config) instead of default fs singleton
  • createFS(config) and getDefaultFS() helpers available
  • Requires crossOriginIsolated for sync API (async API works everywhere)
  • Complete internal rewrite — not backwards compatible with v2 internals

v2.0.0 (2025)

Major rewrite with sync API support via OPFS sync access handles and performance tiers.

v1.0.0 (2024)

Initial release — async-only OPFS filesystem with fs.promises API.

Contributing

git clone https://github.com/componentor/fs
cd fs
npm install
npm run build       # Build the library
npm test            # Run unit tests (107 tests)
npm run benchmark:open  # Run benchmarks in browser

License

MIT