Skip to content

jeffy-g/universal-fs

Repository files navigation

@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

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

  • .txt β†’ text/plain
  • .json β†’ application/json
  • .html β†’ text/html
  • .css β†’ text/css
  • .js β†’ application/javascript
  • .ts β†’ application/typescript

Image Formats

  • .png β†’ image/png
  • .jpg, .jpeg β†’ image/jpeg
  • .gif β†’ image/gif
  • .webp β†’ image/webp
  • .svg β†’ image/svg+xml

Audio/Video Formats

  • .mp3 β†’ audio/mpeg
  • .wav β†’ audio/wav
  • .mp4 β†’ video/mp4
  • .mid, .midi β†’ audio/midi

Archive Formats

  • .zip β†’ application/zip
  • .tar β†’ application/x-tar
  • .gz β†’ application/gzip
  • .als β†’ application/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

  • File object support in browsers (drag & drop, input files) βœ… v0.0.10
  • Enhanced type inference system βœ… v0.0.10
  • 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.

About

Universal file system utilities for Node.js and Browser environments

Resources

License

Stars

Watchers

Forks

Packages

No packages published