Universal file system utilities for Node.js and Browser environments
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.
- π 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
npm install @jeffy-g/universal-fs
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);
// 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");
(async () => {
const { ufs } = await import('@jeffy-g/universal-fs');
const result = await ufs.readText('hello.txt');
console.log(result);
})();
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.
<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>
<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>
<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 oflatest
).
or use THIS -> https://www.srihash.org/
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" });
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" });
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 | Basic | Basic | Uses Node.js compatibility layer | |
Deno | Basic | Basic | Experimental support |
- 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
- Node.js:
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" });
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
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 metadataTUFSInputType
=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
/**
* 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}`;
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`);
// 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`);
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
};
}
}
// 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);
}
- Reading: Direct filesystem access using
fs.promises
- Writing: Creates directories automatically, writes to disk
- Binary Support: Full support including
Buffer
andbinary
format - Blob Support: Requires Node.js v15.7.0 or higher for the
Blob
API.
On older versions,ufs.readBlob()
andufs.writeBlob()
will return aBuffer
instead of aBlob
.
- Reading:
- HTTP(S) URLs via
fetch()
API File
objects from input elements or drag & dropBlob
objects created programmatically
- HTTP(S) URLs via
- 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)
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
}
}
universal-fs automatically detects the MIME type from the file extension when performing read/write operations. The library includes comprehensive MIME type mappings:
.txt
βtext/plain
.json
βapplication/json
.html
βtext/html
.css
βtext/css
.js
βapplication/javascript
.ts
βapplication/typescript
.png
βimage/png
.jpg
,.jpeg
βimage/jpeg
.gif
βimage/gif
.webp
βimage/webp
.svg
βimage/svg+xml
.mp3
βaudio/mpeg
.wav
βaudio/wav
.mp4
βvideo/mp4
.mid
,.midi
βaudio/midi
.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
- 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
- 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
Contributions are welcome! Please check our Contributing Guide for details.
MIT Β© jeffy-g
Need help? Check out our examples or open an issue.