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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@jeffy-g/universal-fs

v0.4.2

Published

Universal file system utils for Node.js and Browser in TypeScript.

Downloads

43

Readme

@jeffy-g/universal-fs

Universal file system utilities for Node.js and Browser environments

Node.js CI npm version License: MIT TypeScript

A lightweight, TypeScript-first library that provides consistent file I/O operations across different JavaScript environments. Write once, run everywhere – whether you're building for Node.js servers or browser applications.

✨ Features

  • 🌍 Universal: Works seamlessly in Node.js and browser environments
  • 📝 Type-safe: Full TypeScript support with comprehensive type definitions and advanced type inference
  • 🔄 Format-aware: Converts input based on the explicitly specified format (text, json, binary, blob, arrayBuffer). No automatic detection is performed.
  • 📁 Smart: Auto-creates directories in Node.js, triggers downloads in browsers
  • 🎯 Consistent: Same API across all supported environments
  • 🚀 Modern: Built with ESM-first approach using native APIs
  • 🔗 Flexible Input: Supports URLs, File objects, and Blob objects in browsers
  • Lazy Loading: Optimized bundle size with environment-specific lazy loading

🚀 Quick Start

npm install @jeffy-g/universal-fs

ufs is the main entry point implementing the universal-fs API (IUniversalFs).

All file operations (readFile, writeFile, etc.) are accessible via this object.

import { ufs } from "@jeffy-g/universal-fs";

// Write text file
await ufs.writeText("hello.txt", "Hello, World!");

// Read JSON file with type safety
const config = await ufs.readJSON<{ name: string; version: string }>("config.json");
console.log(config.name); // Type-safe access

// Read with detailed metadata
const result = await ufs.readText("hello.txt", { useDetails: true });
console.log(result.filename);  // "hello.txt"
console.log(result.size);      // File size in bytes
console.log(result.strategy);  // "node" or "browser"
console.log(result.data);      // File content

// Write binary data
const buffer = new Uint8Array([72, 101, 108, 108, 111]);
// Both Uint8Array and ArrayBuffer are supported
await ufs.writeBuffer("data.bin", buffer);
await ufs.writeBuffer("data2.bin", buffer.buffer);

Browser-specific features

// Read from File input
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const content = await ufs.readText(file);

// Read from Blob
const blob = new Blob(["Hello World"], { type: "text/plain" });
const text = await ufs.readText(blob);

// Read from URL
const data = await ufs.readJSON("https://api.example.com/config.json");

Using in CommonJS

(async () => {
  const { ufs } = await import('@jeffy-g/universal-fs');
  const result = await ufs.readText('hello.txt');
  console.log(result);
})();

🌐 CDN Usage

You can load @jeffy-g/universal-fs directly via CDN:

Note: When loading via CDN (<script type="module">), TypeScript type inference is not available.
For full type support, install via npm or bun and use in a TypeScript project.

✅ ESM via jsDelivr

<script type="module">
  const mod = await import("https://cdn.jsdelivr.net/npm/@jeffy-g/universal-fs@latest/dist/index.js");
  const { ufs } = mod;
  const result = await ufs.readFile("https://example.com/data.json", { format: "json" });
  console.log(result);
</script>

✅ Optimized ESM (Recommended)

<script type="module">
  import { ufs } from "https://cdn.jsdelivr.net/npm/@jeffy-g/universal-fs@latest/+esm";
  const result = await ufs.readFile("https://example.com/data.json", { format: "json" });
  console.log(result);
</script>

✅ With SRI (Subresource Integrity)

<script type="module" integrity="sha384-xxxxxxxx" crossorigin="anonymous">
  import { ufs } from "https://cdn.jsdelivr.net/npm/@jeffy-g/universal-fs@latest/+esm";
</script>

To get the sha384 hash:

curl -sL "https://cdn.jsdelivr.net/npm/@jeffy-g/[email protected]/+esm" | openssl dgst -sha384 -binary | openssl base64 -A

Important: Always use a fixed version when using SRI (e.g., @0.1.0 instead of latest).
or use THIS -> https://www.srihash.org/

🦕 Deno

You can import via npm: specifier or CDN:

// Using npm:
import { ufs } from "npm:@jeffy-g/[email protected]";
// Using CDN (jsDelivr):
import { ufs } from "https://cdn.jsdelivr.net/npm/@jeffy-g/[email protected]/+esm";
const result = await ufs.readFile("https://example.com/file.json", { format: "json" });

🍞 Bun

Install and use:

bun add @jeffy-g/universal-fs
import { ufs } from "@jeffy-g/universal-fs";
const result = await ufs.readFile("https://example.com/file.json", { format: "json" });

🌐 Platform Support

| Environment | Status | Read Support | Write Support | Notes | |-------------|--------|--------------|---------------|-------| | Node.js | ✅ Full | Local files | File system | Complete filesystem access | | Browser | ✅ Full | URLs, File, Blob | Download trigger | Secure, sandboxed environment | | Bun | ⚠️ Limited | Basic | Basic | Uses Node.js compatibility layer | | Deno | ⚠️ Limited | Basic | Basic | Experimental support |

Environment-specific Notes:

  • Node.js: Full filesystem access with automatic directory creation
  • Browser: read* supports URLs (via fetch), File objects, and Blob objects; write* triggers secure file downloads
  • Input Types:
    • Node.js: string (file paths)
    • Browser: string (URLs), File, Blob

📚 API Reference

Core Methods

readFile<T>(filename, options?)

Universal file reader with automatic format detection and advanced type inference.

// Read as text (default, inferred from no format specified)
const textResult = await ufs.readFile("document.txt");
// Type: string

// Read as JSON with type safety
const jsonResult = await ufs.readFile("config.json", { format: "json" });
// Type: Record<string, unknown> | unknown[] | object

// Explicit type parameter for JSON
const typedResult = await ufs.readFile<{name: string}>("config.json", { format: "json" });
// Type: {name: string}

// Read binary data
const binaryResult = await ufs.readFile("image.png", { format: "arrayBuffer" });
// Type: ArrayBuffer

// Read with detailed metadata
const detailedResult = await ufs.readFile("data.txt", { useDetails: true });
// Type: TUFSResult<string> with filename, size, strategy, etc.

// Browser: Read from File object
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const file = fileInput.files![0];
const content = await ufs.readFile(file, { format: "text" });

writeFile(filename, data, options?)

Universal file writer with smart environment handling and download timeout protection.

// In Node.js: writes to filesystem with automatic directory creation
// In Browser: triggers secure download with 30-second timeout
await ufs.writeFile("output.txt", "Hello World");

// Write with detailed result
const result = await ufs.writeFile("output.txt", "Hello World", { useDetails: true });
console.log(result.size);      // File size
console.log(result.strategy);  // "node" or "browser"
console.log(result.timestamp); // Operation timestamp

Convenience Methods

| Method | Input Type | Returns | Description | |--------|------------|---------|-------------| | readText() | TUFSInputType | string | Read file as UTF-8 text | | readJSON<T>() | string | T | Parse JSON with type safety | | readBlob() | TUFSInputType | Blob | Read as Blob object | | readBuffer() | TUFSInputType | ArrayBuffer | Read as raw binary data | | writeText() | string, string | void | Write UTF-8 text | | writeJSON() | string, any | void | Serialize and write JSON | | writeBlob() | string, Blob | void | Write Blob data | | writeBuffer() | string, ArrayBuffer \| Uint8Array | void | Write binary data |

Note:

  • All read methods support the useDetails option to return metadata
  • TUFSInputType = string | File | Blob (environment-dependent)
  • The readJSON<T>() method supports object, array, and custom types
  • All write methods support both simple and detailed return modes

Environment Helpers

selectFromEnv(key, cb)

Environment-aware helper for reading simple flags across Node.js and browser runtimes.

  • Node.js: reads from process.env[key]
  • Browser: reads from globalThis[key] (e.g. window.FEATURE_FLAG)
  • Other environments: passes undefined to the callback
import { selectFromEnv } from "@jeffy-g/universal-fs";

// ✅ Toggle a feature flag: "1" -> enabled
const featureEnabled = selectFromEnv("FEATURE_EXPORT", (flag?: "1" | "0") => {
  return flag === "1";
});

// ✅ Use Case 2: Explicitly specifying the type
const featureEnabled2 = selectFromEnv<"1" | "0", boolean>(
  "FEATURE_EXPORT",
  (flag) => {
    return flag === "1";
  }
);
// ✅ Use Case 3: More complex return types
const config = selectFromEnv("APP_ENV", (env?: "dev" | "prod" | "test") => {
  return {
    isDev: env === "dev",
    isProd: env === "prod",
    logLevel: env === "prod" ? "error" : "debug"
  };
}); // { isDev: boolean; isProd: boolean; logLevel: string }

// ✅ Use Case 4: Filtering Types
const port = selectFromEnv<`${number}`, number>("PORT", (p) => {
  return p ? parseInt(p, 10) : 3000;
});

This keeps environment branching logic localized and type-safe while avoiding direct checks against ufs.env in application code.

Options & Types

/**
 * Input types supported by universal-fs
 */
export type TUFSInputType = string | File | Blob;

/**
 * Format-keyed mapping to the corresponding TypeScript type for file data.
 */
export interface IUFSFormatMap {
  text: string;
  json: Record<string, unknown> | unknown[] | object;
  arrayBuffer: ArrayBuffer;
  blob: Blob;
  binary: Uint8Array; // Node.js Buffer is sub-class of Uint8Array
}

/**
 * Supported format types
 */
export type TUFSFormat = keyof IUFSFormatMap;
// "text" | "json" | "arrayBuffer" | "blob" | "binary"

/**
 * Supported data types for universal file operations.
 */
export type TUFSData = IUFSFormatMap[keyof IUFSFormatMap];

/**
 * Options used for both reading and writing files universally.
 */
export type TUFSOptions = {
  /**
   * Character encoding used for reading and writing operations.
   * Default: "utf8"
   */
  encoding?: BufferEncoding;
  /** Format used when reading (ignored on write). */
  format?: TUFSFormat;
  /** Return detailed metadata including size, strategy, timestamp */
  useDetails?: true;
};

/**
 * Generic result type for universal file operations.
 * @template T - Optional data returned when reading.
 */
export type TUFSResult<T extends TUFSData | undefined = undefined> = {
  filename: string;
  size: number;
  strategy: "node" | "browser";
  timestamp: number;
  path?: string;     // Node.js only
  url?: string;      // Browser only  
  mimeType?: TMimeType; // Inferred MIME type
} & ([T] extends [undefined] ? {} : { data: T });

/**
 * MIME type representation
 */
export type TMimeType = `${string}/${string}`;

🏗️ Advanced Examples

Configuration Management

interface AppConfig {
  apiUrl: string;
  features: string[];
  debug: boolean;
}

// Read configuration with type safety
const config = await ufs.readJSON<AppConfig>("app-config.json");

// Update and save
config.debug = false;
await ufs.writeJSON("app-config.json", config);

// Read with metadata
const configWithMeta = await ufs.readJSON<AppConfig>("app-config.json", { useDetails: true });
console.log(`Config loaded from ${configWithMeta.strategy} environment`);
console.log(`File size: ${configWithMeta.size} bytes`);

Binary Data Processing

// Read image file
const imageBuffer = await ufs.readBuffer("photo.jpg");

// Process the binary data
const processedData = processImage(imageBuffer);

// Save processed result with metadata
const result = await ufs.writeBuffer("processed-photo.jpg", processedData, { useDetails: true });
console.log(`Processed image saved: ${result.size} bytes`);

Cross-Platform File Utilities

class FileManager {
  static async backup<T>(filename: string): Promise<void> {
    const original = await ufs.readFile(filename);
    const backupName = `${filename}.backup.${Date.now()}`;
    await ufs.writeFile(backupName, original);
  }
  
  static async migrate(oldPath: string, newPath: string): Promise<void> {
    const content = await ufs.readFile(oldPath);
    await ufs.writeFile(newPath, content);
    // Note: Deletion not supported in browser environment
  }
  
  static async getFileInfo(filename: string) {
    const result = await ufs.readFile(filename, { useDetails: true });
    return {
      name: result.filename,
      size: result.size,
      mimeType: result.mimeType,
      environment: result.strategy
    };
  }
}

Browser File Handling

// Handle file input changes
document.getElementById('fileInput')?.addEventListener('change', async (e) => {
  const target = e.target as HTMLInputElement;
  const file = target.files?.[0];
  
  if (file) {
    try {
      // Read file content
      const content = await ufs.readText(file);
      console.log('File content:', content);
      
      // Get file info
      const info = await ufs.readFile(file, { useDetails: true });
      console.log(`File: ${info.filename}, Size: ${info.size} bytes`);
      
    } catch (error) {
      console.error('Failed to read file:', error);
    }
  }
});

// Process and download modified content
async function processAndDownload(originalFile: File) {
  const content = await ufs.readText(originalFile);
  const processed = content.toUpperCase(); // Example processing
  
  // This will trigger a download in the browser
  await ufs.writeText(`processed_${originalFile.name}`, processed);
}

🔧 Environment Behavior (Node.js 15.7.0+ required for Blob support)

Node.js Environment

  • Reading: Direct filesystem access using fs.promises
  • Writing: Creates directories automatically, writes to disk
  • Binary Support: Full support including Buffer and binary format
  • Blob Support: Requires Node.js v15.7.0 or higher for the Blob API.
    On older versions, ufs.readBlob() and ufs.writeBlob() will return a Buffer instead of a Blob.

Browser Environment

  • Reading:
    • HTTP(S) URLs via fetch() API
    • File objects from input elements or drag & drop
    • Blob objects created programmatically
  • Writing: Triggers secure file downloads via Blob URLs with:
    • 30-second timeout protection
    • Automatic cleanup of object URLs
    • Filename sanitization for security
  • Limitations: No direct filesystem access (security restrictions)

Error Handling

import { UniversalFsError } from "@jeffy-g/universal-fs";

try {
  const result = await ufs.readFile("nonexistent.txt");
} catch (error) {
  if (error instanceof UniversalFsError) {
    console.log(`Operation: ${error.operation}`);    // "read" | "write"
    console.log(`Strategy: ${error.strategy}`);      // "node" | "browser"
    console.log(`Filename: ${error.filename}`);      // File that caused error
    console.log(`Original cause:`, error.cause);     // Original error object
  }
}

🔍 MIME Type Handling

universal-fs automatically detects the MIME type from the file extension when performing read/write operations. The library includes comprehensive MIME type mappings:

Text Formats

  • .txttext/plain
  • .jsonapplication/json
  • .htmltext/html
  • .csstext/css
  • .jsapplication/javascript
  • .tsapplication/typescript

Image Formats

  • .pngimage/png
  • .jpg, .jpegimage/jpeg
  • .gifimage/gif
  • .webpimage/webp
  • .svgimage/svg+xml

Audio/Video Formats

  • .mp3audio/mpeg
  • .wavaudio/wav
  • .mp4video/mp4
  • .mid, .midiaudio/midi

Archive Formats

  • .zipapplication/zip
  • .tarapplication/x-tar
  • .gzapplication/gzip
  • .alsapplication/gzip (Ableton format)

If the extension is unknown, it defaults to:

application/octet-stream

⚠️ Current Limitations

  • Browser Security: Only supports URLs accessible via CORS and File/Blob objects
  • Bun/Deno: Limited testing, may have compatibility issues
  • Blob Support: Node.js requires v15.7.0+ for full Blob support
  • Download Timeout: Browser downloads have a 30-second timeout limit

🛣️ Roadmap

  • [x] File object support in browsers (drag & drop, input files) ✅ v0.0.10
  • [x] Enhanced type inference system ✅ v0.0.10
  • [x] Detailed metadata support with useDetails option ✅ v0.0.10
  • [ ] Stream-based operations for large files (planned for v0.2.x)
  • [ ] Enhanced Bun and Deno compatibility
  • [ ] Directory operations (list, create, remove)
  • [ ] Compression/decompression utilities
  • [ ] Progress callbacks for large operations
  • [ ] Custom MIME type override options

🤝 Contributing

Contributions are welcome! Please check our Contributing Guide for details.

📄 License

MIT © jeffy-g


Need help? Check out our examples or open an issue.