Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 104 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,67 @@ selected parts of `node:fs/promises` as a foundation for core operations,
strategically diverging to offer performance optimizations or broader
compatibility.

For browser environments, the library uses [ZenFS](https://zenfs.dev/core/) to
provide a Node.js-compatible filesystem API with support for various storage
backends including in-memory, IndexedDB, and more.

For cross rutime _path_ operations, [jsr.io/@std/path](https://jsr.io/@std/path)
will cover most scenarios, this library focuses on the file system operations.

## Browser Support

This library supports browser environments through ZenFS, which provides a
Node.js-compatible filesystem API. By default, an in-memory filesystem is used,
but you can configure different backends such as IndexedDB for persistent
storage.

### Basic Browser Usage

```ts
import { readFile, writeFile } from "@cross/fs/io";
import { exists, mkdir } from "@cross/fs";

// Use filesystem operations - in-memory by default
await mkdir("/data");
await writeFile("/data/test.txt", "Hello from the browser!");
console.log(await readFile("/data/test.txt", "utf8"));
// "Hello from the browser!"
```

### Configuring Persistent Storage

For persistent browser storage using IndexedDB or other backends:

```ts
import { configureBrowserFS } from "@cross/fs";

// Import ZenFS backends you want to use
// Note: Install @zenfs/dom separately for IndexedDB and WebAccess backends
const { IndexedDB, InMemory } = await import("@zenfs/dom");

// Configure before using filesystem operations
await configureBrowserFS({
mounts: {
'/storage': { backend: IndexedDB }, // Persistent storage
'/tmp': { backend: InMemory } // Temporary in-memory storage
}
});

// Now use the filesystem with persistent storage
import { writeFile, readFile } from "@cross/fs/io";
await writeFile("/storage/config.json", JSON.stringify({ theme: "dark" }));
```

### Browser Limitations

Some operations are not available or have limitations in browser environments:

- `chdir()` - Not supported (throws error)
- `cwd()` - Always returns "/"
- `which()` - Not applicable in browsers
- File permissions (chmod, chown) - Limited or not supported
- Symbolic links - Depends on ZenFS backend

## Modules

### Stat
Expand Down Expand Up @@ -42,19 +100,19 @@ console.log(await which("bun"));

Methods:

| Method | Deno | Node | Bun | Base implementation |
| --------- | ---- | ---- | --- | ------------------- |
| stat | X | X | X | runtime native |
| lstat | X | X | X | node:fs/promises |
| exists | X | X | X | custom |
| isDir | X | X | X | custom |
| isFile | X | X | X | custom |
| isSymlink | X | X | X | custom |
| size | X | X | X | custom |
| find | X | X | X | custom |
| diskusage | X | X | X | custom |
| hash | X | X | X | custom |
| which | X | X | X | custom |
| Method | Deno | Node | Bun | Browser | Base implementation |
| --------- | ---- | ---- | --- | ------- | ------------------- |
| stat | X | X | X | X | runtime native |
| lstat | X | X | X | X | node:fs/promises |
| exists | X | X | X | X | custom |
| isDir | X | X | X | X | custom |
| isFile | X | X | X | X | custom |
| isSymlink | X | X | X | X | custom |
| size | X | X | X | X | custom |
| find | X | X | X | X | custom |
| diskusage | X | X | X | - | custom |
| hash | X | X | X | X | custom |
| which | X | X | X | - | custom |

### Io

Expand All @@ -69,11 +127,10 @@ console.log(await readFile("my/file"));

Methods:

| Method | Deno | Node | Bun | Base implementation |
| ---------- | ---- | ---- | --- | ------------------- |
| appendFile | X | X | X | node:fs/promises |
| readFile | X | X | X | node:fs/promises |
| writeFile | X | X | X | node:fs/promises |
| Method | Deno | Node | Bun | Browser | Base implementation |
| ---------- | ---- | ---- | --- | ------- | ------------------- |
| readFile | X | X | X | X | node:fs/promises |
| writeFile | X | X | X | X | node:fs/promises |

### Ops

Expand All @@ -91,38 +148,38 @@ console.log(await dirpath("config"));

Methods:

| Method | Deno | Node | Bun | Base implementation |
| --------- | ---- | ---- | --- | ------------------- |
| FsWatcher | X | X | X | custom |
| unlink | X | X | X | node:fs/promises |
| dirpath | X | X | X | @cross/dir |
| mkdir | X | X | X | node:fs/promises |
| cwd | X | X | X | custom |
| chdir | X | X | X | custom |
| mktempdir | X | X | X | custom |
| rm | X | X | X | node:fs/promises |
| rmdir | X | X | X | node:fs/promises |
| cp | X | X | X | node:fs/promises |
| tempfile | X | X | X | custom |
| link | X | X | X | node:fs/promises |
| unlink | X | X | X | node:fs/promises |
| readdir | X | X | X | node:fs/promises |
| readlink | X | X | X | node:fs/promises |
| realpath | X | X | X | node:fs/promises |
| rename | X | X | X | node:fs/promises |
| chmod | X | X | X | node:fs/promises |
| chown | X | X | X | node:fs/promises |
| rename | X | X | X | node:fs/promises |
| truncate | X | X | X | node:fs/promises |
| open | X | X | X | node:fs/promises |
| access | X | X | X | node:fs/promises |
| constants | X | X | X | node:fs/promises |
| Method | Deno | Node | Bun | Browser | Base implementation |
| --------- | ---- | ---- | --- | ------- | ------------------- |
| FsWatcher | X | X | X | - | custom |
| unlink | X | X | X | X | node:fs/promises |
| dirpath | X | X | X | - | @cross/dir |
| mkdir | X | X | X | X | node:fs/promises |
| cwd | X | X | X | X* | custom |
| chdir | X | X | X | - | custom |
| mktempdir | X | X | X | X | custom |
| rm | X | X | X | X | node:fs/promises |
| rmdir | X | X | X | X | node:fs/promises |
| cp | X | X | X | X | node:fs/promises |
| tempfile | X | X | X | X | custom |
| link | X | X | X | - | node:fs/promises |
| readdir | X | X | X | X | node:fs/promises |
| readlink | X | X | X | - | node:fs/promises |
| realpath | X | X | X | - | node:fs/promises |
| rename | X | X | X | X | node:fs/promises |
| chmod | X | X | X | - | node:fs/promises |
| chown | X | X | X | - | node:fs/promises |
| truncate | X | X | X | - | node:fs/promises |
| open | X | X | X | - | node:fs/promises |
| access | X | X | X | X | node:fs/promises |
| constants | X | X | X | X | node:fs/promises |

*Browser: `cwd()` always returns "/"

Types:

| Method | Deno | Node | Bun | Base implementation |
| --------- | ---- | ---- | --- | ------------------- |
| FSWatcher | X | X | X | node:fs/promises |
| Method | Deno | Node | Bun | Browser | Base implementation |
| --------- | ---- | ---- | --- | ------- | ------------------- |
| FSWatcher | X | X | X | - | node:fs/promises |

Examples:

Expand Down
3 changes: 2 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"@cross/test": "jsr:@cross/test@^0.0.10",
"@cross/utils": "jsr:@cross/utils@^0.16.0",
"@std/assert": "jsr:@std/assert@^1.0.12",
"@std/path": "jsr:@std/path@^1.0.8"
"@std/path": "jsr:@std/path@^1.0.8",
"@zenfs/core": "npm:@zenfs/core@^1.0.9"
},
"publish": {
"exclude": [".github", "*.test.ts"]
Expand Down
26 changes: 25 additions & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/**
* @cross/fs - A cross-runtime utility library for file system operations
*
* This package provides cross-runtime compatible file system operations for Node.js, Deno, and Bun.
* This package provides cross-runtime compatible file system operations for Node.js, Deno, Bun, and browsers.
* It includes utilities for file/directory operations, file system watching, and common file system tasks.
*
* For browser environments, it uses ZenFS to provide a Node.js-compatible filesystem API.
*
* @example
* ```typescript
* import { exists, find, hash } from "@cross/fs";
Expand All @@ -18,9 +20,31 @@
* const fileHash = await hash("README.md", "sha256");
* ```
*
* @example Browser usage with custom ZenFS configuration
* ```typescript
* import { configureBrowserFS } from "@cross/fs";
* import { IndexedDB } from "@zenfs/dom";
*
* // Configure ZenFS before using filesystem operations
* await configureBrowserFS({
* mounts: {
* '/storage': IndexedDB,
* '/tmp': { backend: InMemory }
* }
* });
*
* // Now use filesystem operations normally
* import { writeFile, readFile } from "@cross/fs/io";
* await writeFile("/storage/myfile.txt", "Hello browser!");
* console.log(await readFile("/storage/myfile.txt", "utf8"));
* ```
*
* @module
*/

export * from "./src/stat/mod.ts";
export * from "./src/io/mod.ts";
export * from "./src/ops/mod.ts";

// Export browser configuration utilities
export { configureBrowserFS, getBrowserFS, isBrowserFSInitialized } from "./src/utils/browser-fs.ts";
61 changes: 59 additions & 2 deletions src/io/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
* @cross/fs/io - File input/output operations
*
* This module provides cross-runtime compatible functions for reading and writing files.
* It re-exports the core file I/O functions from node:fs/promises for consistency.
* It re-exports the core file I/O functions from node:fs/promises for Node.js/Deno/Bun,
* and uses ZenFS for browser environments.
*
* @example
* ```typescript
Expand All @@ -18,4 +19,60 @@
* @module
*/

export { readFile, writeFile } from "node:fs/promises";
import { CurrentRuntime, Runtime } from "@cross/runtime";

/**
* Reads the entire contents of a file.
*
* @param path - The path to the file
* @param options - Optional encoding or options object
* @returns The contents of the file
*/
export async function readFile(
path: string | URL,
options?: { encoding?: null; flag?: string } | null
): Promise<Uint8Array>;
export async function readFile(
path: string | URL,
options: { encoding: BufferEncoding; flag?: string } | BufferEncoding
): Promise<string>;
export async function readFile(
path: string | URL,
options?: { encoding?: BufferEncoding | null; flag?: string } | BufferEncoding | null
): Promise<string | Uint8Array> {
if (CurrentRuntime === Runtime.Browser) {
const { getBrowserFS } = await import("../utils/browser-fs.ts");
const fs = await getBrowserFS();
//@ts-ignore ZenFS typing
return await fs.promises.readFile(path, options);
} else {
const { readFile: nodeReadFile } = await import("node:fs/promises");
//@ts-ignore Cross-runtime typing
return await nodeReadFile(path, options);
}
}

/**
* Writes data to a file, replacing the file if it already exists.
*
* @param path - The path to the file
* @param data - The data to write
* @param options - Optional encoding or options object
*/
export async function writeFile(
path: string | URL,
data: string | Uint8Array,
options?: { encoding?: BufferEncoding | null; mode?: number; flag?: string } | BufferEncoding | null
): Promise<void> {
if (CurrentRuntime === Runtime.Browser) {
const { getBrowserFS } = await import("../utils/browser-fs.ts");
const fs = await getBrowserFS();
//@ts-ignore ZenFS typing
await fs.promises.writeFile(path, data, options);
} else {
const { writeFile: nodeWriteFile } = await import("node:fs/promises");
//@ts-ignore Cross-runtime typing
await nodeWriteFile(path, data, options);
}
}

5 changes: 5 additions & 0 deletions src/ops/chdir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { CurrentRuntime, Runtime } from "@cross/runtime";
/**
* Changes the current working directory in a cross-runtime compatible manner.
*
* Note: This operation is not supported in browser environments as browsers don't have
* a mutable working directory concept. Calling this in a browser will throw an error.
*
* @param {string} path - The new working directory path.
* @throws If the directory change fails or unsupported runtime is encountered.
* @example
Expand All @@ -25,6 +28,8 @@ export function chdir(path: string): void {
) {
//@ts-ignore cross-runtime
process.chdir(path);
} else if (CurrentRuntime === Runtime.Browser) {
throw new Error("chdir is not supported in browser environments");
} else {
throw new Error("Cannot change directory in the current runtime.");
}
Expand Down
6 changes: 6 additions & 0 deletions src/ops/cwd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { CurrentRuntime, Runtime } from "@cross/runtime";
/**
* Returns the current working directory in a cross-runtime compatible manner.
*
* Note: In browser environments, this returns "/" as browsers don't have a traditional
* working directory concept.
*
* @returns {string} The current working directory path.
* @throws
* @example
Expand All @@ -21,6 +24,9 @@ export function cwd(): string {
) {
//@ts-ignore cross-runtime
return process.cwd();
} else if (CurrentRuntime === Runtime.Browser) {
// In browser, return root path as there's no traditional cwd concept
return "/";
} else {
throw new Error(
"Cannot determine working directory using current runtime.",
Expand Down
Loading
Loading