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

onefs

v0.6.2

Published

Cross-platform file system abstraction for web, Tauri, and Capacitor

Readme

onefs

Cross-platform file system abstraction for web, Tauri, and Capacitor.

Features

  • File System Access API with handle persistence via IndexedDB
  • Fallback mode using file picker + IndexedDB storage
  • Tauri integration via @tauri-apps/plugin-dialog and @tauri-apps/plugin-fs
  • Capacitor integration via @capacitor/filesystem
  • Automatic platform detection with configurable overrides
  • Type-safe error handling with discriminated result types
  • Lazy directory loading - list entries without loading file contents
  • Automatic storage pruning - keeps recent files within configured limit

Installation

npm install onefs
# or
bun add onefs

Quick Start

import { createOneFS } from 'onefs'

const fs = createOneFS({ appName: 'myapp' })

// Open a file
const result = await fs.openFile({ accept: ['.json', '.txt'] })
if (result.ok) {
  const text = fs.readAsText(result.data)
  console.log(text)
} else {
  if (result.error.code === 'cancelled') {
    console.log('User cancelled')
  } else {
    console.error(result.error.message)
  }
}

// Save to the same file
const saveResult = await fs.saveFile(file, 'updated content')

// Save as new file
const newFile = await fs.saveFileAs('content', {
  suggestedName: 'document.txt'
})

Important: Platform Differences

OneFS abstracts platform differences, but some behaviors vary. Always check capabilities before assuming behavior.

Content is Always Uint8Array

File content is always returned as Uint8Array, never as a string. Use helper methods to convert:

const file = (await fs.openFile()).data

// Convert to string
const text = fs.readAsText(file)

// Parse as JSON
const json = fs.readAsJSON<MyType>(file)

// Get as Blob for images
const blob = fs.readAsBlob(file)

Save Behavior Varies by Platform

The saveFile() method behaves differently depending on the platform:

| Platform | Behavior | |----------|----------| | web-fs-access | Saves in-place to original file location | | tauri | Saves in-place to original file location | | web-fallback | Triggers a download (cannot save in-place) | | capacitor | Saves to app's Data directory (not original location) |

Check capabilities.canSaveInPlace to detect this:

if (fs.capabilities.canSaveInPlace) {
  // Will save to original location
  await fs.saveFile(file, newContent)
} else {
  // Will trigger download or save to app directory
  // Consider showing a different UI
  await fs.saveFile(file, newContent)
}

Path Property Varies

The file.path property has different meanings:

| Platform | file.path value | |----------|------------------| | web-fs-access | undefined (no path access in browser) | | web-fallback | undefined | | tauri | Real filesystem path (e.g., /home/user/doc.txt) | | capacitor | Synthetic identifier (e.g., onefs_123_doc.txt) |

Directory Support

Directory operations are not available on all platforms:

| Platform | openDirectory | readDirectory | |----------|-----------------|-----------------| | web-fs-access | Full support | Full support | | web-fallback | Not supported | Not supported | | tauri | Full support | Full support | | capacitor | Documents only | Documents only |

if (fs.supportsDirectories) {
  const dir = await fs.openDirectory()
  // ...
}

Directory Operations

Directories are loaded lazily to avoid memory issues with large folders:

// Open directory picker
const dirResult = await fs.openDirectory()
if (!dirResult.ok) return

// List entries (metadata only - no content loaded)
const entriesResult = await fs.readDirectory(dirResult.data)
if (!entriesResult.ok) return

for (const entry of entriesResult.data) {
  console.log(entry.name, entry.kind, entry.size)

  if (entry.kind === 'file') {
    // Load specific file content on demand
    const fileResult = await fs.readFileFromDirectory(dirResult.data, entry)
    if (fileResult.ok) {
      const content = fs.readAsText(fileResult.data)
    }
  }
}

OneFSEntry

Directory entries include metadata without content:

interface OneFSEntry {
  name: string              // "document.txt" or "subfolder"
  kind: 'file' | 'directory'
  size?: number             // File size in bytes (files only)
  lastModified?: number     // Timestamp (files only)
  path?: string             // Full path (Tauri/Capacitor only)
  handle?: FileSystemHandle // Native handle (web-fs-access only)
}

Error Handling

All async operations return OneFSResult<T>:

type OneFSResult<T> =
  | { ok: true; data: T }
  | { ok: false; error: OneFSError }

interface OneFSError {
  code: OneFSErrorCode
  message: string
  cause?: unknown  // Original error if available
}

type OneFSErrorCode =
  | 'cancelled'           // User cancelled operation
  | 'permission_denied'   // No permission to access file/directory
  | 'not_supported'       // Operation not supported on this platform
  | 'not_found'           // File/handle not found
  | 'io_error'            // Generic I/O error
  | 'unknown'             // Unknown error

Example:

const result = await fs.openFile()

if (!result.ok) {
  switch (result.error.code) {
    case 'cancelled':
      // User clicked cancel - not an error
      break
    case 'permission_denied':
      showPermissionDialog()
      break
    case 'not_supported':
      showFallbackUI()
      break
    default:
      console.error('Failed:', result.error.message)
  }
  return
}

const file = result.data

Configuration

const fs = createOneFS({
  appName: 'myapp',           // Required - used for IndexedDB database name
  maxRecentFiles: 10,         // Max files to remember (default: 10)
  persistByDefault: true,     // Store files/handles in IndexedDB (default: true)
  useNativeFSAccess: true,    // Use File System Access API when available (default: true)
  preferredAdapter: 'tauri',  // Force specific adapter (optional)
})

Per-Operation Options

// Don't persist this file to recent list
const file = await fs.openFile({ persist: false })

// Save without adding to recent
await fs.saveFileAs(content, { persist: false })

Platform Detection

console.log(fs.platform)
// 'web-fs-access' | 'web-fallback' | 'tauri' | 'capacitor'

console.log(fs.capabilities)
// {
//   openFile: true,
//   saveFile: true,
//   saveFileAs: true,
//   openDirectory: true,
//   readDirectory: true,
//   handlePersistence: true,
//   canSaveInPlace: true,
// }

console.log(fs.supportsDirectories)      // boolean
console.log(fs.supportsHandlePersistence) // boolean

Platform Capabilities Matrix

| Capability | web-fs-access | web-fallback | tauri | capacitor | |------------|---------------|--------------|-------|-----------| | openFile | Yes | Yes | Yes | Yes | | saveFile | Yes | Yes (download) | Yes | Yes (app dir) | | saveFileAs | Yes | Yes (download) | Yes | Yes (app dir) | | openDirectory | Yes | No | Yes | Limited | | readDirectory | Yes | No | Yes | Limited | | handlePersistence | Yes | No | No | No | | canSaveInPlace | Yes | No | Yes | No |

OneFSFile

interface OneFSFile {
  id: string              // Unique identifier
  name: string            // File name (e.g., "document.txt")
  path?: string           // Full path (Tauri/Capacitor only)
  content: Uint8Array     // File content as bytes
  mimeType: string        // MIME type (e.g., "text/plain")
  size: number            // File size in bytes
  lastModified: number    // Timestamp (ms since epoch)
  handle?: FileSystemFileHandle  // Native handle (web-fs-access only)
}

Helper Methods

fs.readAsText(file)       // string (UTF-8)
fs.readAsJSON(file)       // parsed JSON
fs.readAsDataURL(file)    // data:mime;base64,...
fs.readAsBlob(file)       // Blob
fs.readAsObjectURL(file)  // blob:... (remember to revoke!)

Recent Files

// Get recent files
const recent = await fs.getRecentFiles()
// Returns StoredHandle[] with { id, name, path?, type, storedAt }

// Restore a file
const file = await fs.restoreFile(recent[0])

// On web-fs-access: Re-reads from disk (may prompt for permission)
// On other platforms: Returns cached content from IndexedDB

// Restore a directory with specific permission mode
const dir = await fs.restoreDirectory(recent[0], 'readwrite')

// Remove from recent
await fs.removeFromRecent(id)

// Clear all
await fs.clearRecent()

Permission Management (web-fs-access only)

Check and request permissions on files and directories:

// Check current permission status
const status = await fs.queryPermission(directory, 'readwrite')
// Returns: 'granted' | 'denied' | 'prompt'

// Request permission (must be called during user gesture)
const result = await fs.requestPermission(directory, 'readwrite')
if (result.ok) {
  // Permission granted
}

// On non-web-fs-access platforms, these return 'granted' and ok(true)

Named Directory Storage (web-fs-access only)

Store directories by key, separate from the recent files list. Useful for app preferences like output directories:

// Open and store a directory by key
const dir = await fs.openDirectory({ mode: 'readwrite' })
if (dir.ok) {
  await fs.setNamedDirectory('outputDir', dir.data)
}

// Retrieve later (automatically requests permission)
const stored = await fs.getNamedDirectory('outputDir', 'readwrite')
if (stored.ok) {
  // Use stored.data.handle for file operations
}

// Remove
await fs.removeNamedDirectory('outputDir')

Exports

// Main factory
import { createOneFS, OneFS } from 'onefs'

// Types
import type {
  OneFSFile,
  OneFSDirectory,
  OneFSEntry,
  OneFSResult,
  OneFSError,
  OneFSErrorCode,
  OneFSCapabilities,
  Platform,
  StoredHandle,
} from 'onefs'

// Helpers
import { ok, err, PLATFORM_CAPABILITIES } from 'onefs'

// Individual adapters (for advanced use)
import {
  FSAccessAdapter,
  PickerIDBAdapter,
  TauriAdapter,
  CapacitorAdapter,
} from 'onefs'

Platform-Specific Setup

Tauri

Add the required plugins to your Cargo.toml:

[dependencies]
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"

And initialize them in your Tauri app:

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_fs::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Capacitor

Install the filesystem plugin:

npm install @capacitor/filesystem
npx cap sync

For iOS Files app integration (users can drop files into your app's folder):

Add to your ios/App/App/Info.plist:

<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

This exposes your app's Documents folder in the iOS Files app. Users can drag files there, and your app can scan them with openDirectory() and scanDirectory().

Optional: Install @capawesome/capacitor-file-picker for native file picker (otherwise falls back to HTML input).

Scanning Directories

Recursively scan directories for files with optional filtering:

const dir = await fs.openDirectory()
if (!dir.ok) return

// Scan for specific file types
const result = await fs.scanDirectory(dir.data, {
  extensions: ['.mp3', '.flac', '.wav'],
  skipStats: true,  // Faster - don't fetch size/mtime
  onProgress: (scanned, found) => {
    console.log(`Scanned ${scanned} entries, found ${found} matches`)
  },
  signal: abortController.signal,  // Optional cancellation
})

if (result.ok) {
  for (const entry of result.data) {
    console.log(entry.name, entry.path)
  }
}

Streaming URLs (Tauri/Capacitor)

Get efficient URLs for media playback without loading files into memory:

// From a directory entry (recommended for media apps)
const url = await fs.getEntryUrl(entry)
if (url) {
  audioElement.src = url
}

// From a file object
const url = await fs.getFileUrl(file)

On Tauri/Capacitor, this uses convertFileSrc() for efficient native streaming. On web platforms, falls back to blob URLs.

Future Improvements

The following features are planned but not yet implemented:

  • Streaming support for large files (ReadableStream)
  • File watching for external changes (FileSystemObserver)

License

MIT