ZenFS Core

repository·main·Indexed 19 days ago

https://github.com/zen-fs/core

A cross-platform library that emulates the Node.js filesystem (fs) API, allowing filesystem operations in environments like web browsers. It features a modular backend system supporting storage engines such as IndexedDB, WebStorage, and InMemory, and provides emulations for node:path and node:readline modules.

Tokens
15.4K
Snippets
47
Records
67
Agent score
65%

What's inside @zenfs/core

  1. What is the ZenFS Internal API?

    main

    The Internal API is the foundational interface used to manage file systems and file operations within ZenFS. It ensures that all backends are compatible with the Virtual File System (VFS) and any applications built on top of ZenFS.

    To create a compatible backend, you must instantiate a FileSystem implementation that conforms to this API. This abstraction allows the VFS to interact with diverse storage backends (like in-memory, cloud storage, or local disk) using a standardized set of operations.

  2. What is a VNode and how does it manage data?

    main

    A VNode is the VFS-level representation of a file, acting as the authoritative source for a file's metadata and cached data. It is similar to a Linux struct inode.

    Key Characteristics:

    • Single Authority: Every open Handle for a file shares one VNode. This ensures that metadata changes (like a truncate) are immediately visible across all handles.
    • Data Caching: VNodes use a Resource to cache file data with byte granularity.
    • Write Strategy: Writes are performed only to the cache. The vnode tracks dirty byte ranges. Data is not written to the backend until a sync occurs.
    • Sync Triggers: A vnode is synced when:
      • A handle is synced or closed (fsync, fdatasync, close).
      • The file was opened with O_SYNC, the inode has the Sync flag, or the filesystem has the sync attribute.
      • The filesystem is unmounted.
    • Cache Bypass: Character devices, block devices, and inodes with the DAX flag bypass the data cache, performing reads/writes directly to the backend.
  3. How ZenFS architecture works

    main

    ZenFS is a modular system designed to provide a flexible abstraction over multiple storage backends. It allows applications to interact with files and directories using a unified interface without needing to know the underlying storage mechanism. The architecture is composed of three primary layers:

    1. Virtual File System (VFS): The top layer that emulates the node:fs API. It handles path resolution, manages mounts for different backends, and manages contexts and permissions.
    2. Backends: The middle layer that acts as the glue between the VFS and the actual storage implementations. Backends allow you to configure and use various underlying storage mechanisms.
    3. Internal API: The foundation layer. Both the VFS and backends rely on this. Backend implementations must conform to this API, which allows the VFS to perform operations without needing to understand the specific details of the underlying implementation.
  4. How the VNode Cache (vcache) works

    main

    Each filesystem maintains a VCache to track its vnodes, modeled after Linux's dcache and icache.

    Key Mechanics:

    • Keying: Vnodes are keyed by ino (inode number), meaning hard links naturally share a single vnode.
    • Lifecycle: Vnodes are reference-counted using ref and unref. A vnode remains in the cache as long as it is either referenced (e.g., an open file) or dirty (contains unsynced changes). Once both conditions are met, it is evicted.
    • Path Index: A separate index is maintained for path lookups and is updated during rename, link, and unlink operations.
    • Authoritative Reads: When performing a stat operation, the VFS checks the cache first. A cached vnode's inode takes precedence over the backend if the vnode is dirty.
    const stats = cacheOf(fs).get(path)?.inode ?? fs.statSync(path);
  5. Understand ZenFS security limitations

    main

    Important Security Warning

    Since ZenFS exists purely client-side, it does not constitute a true security boundary. Security mechanisms like permissions enforcement and chroot are handled at the Virtual File System (VFS) level to ensure file operations comply with access control rules before interacting with backends, but they cannot protect against a malicious actor who has control over the client environment itself.

  6. How the Virtual File System (VFS) works in ZenFS

    main

    The Virtual File System (VFS) is an abstraction layer in ZenFS that emulates the Node.js node:fs API. It provides a unified interface for interacting with diverse storage backends (local filesystems, in-memory stores, cloud providers like Google Drive, etc.) by handling path resolution, mounting, and file system contexts.

    Key responsibilities include:

    • Path Translation: Converting user paths into normalized absolute paths and resolving them to the correct mounted backend.
    • node:fs Emulation: Providing full type compatibility with Node.js's built-in file system module.
    • Mount Management: Allowing multiple backends to be mounted simultaneously with support for hot-swapping and per-mount configurations.
    • Contexts: Encapsulating operations within execution scopes to manage permissions and effectively chroot operations.
  7. Understanding the `FileSystem` abstraction

    main

    The FileSystem class is the central component of the internal API. It provides a unified interface for several critical responsibilities:

    • Storage Abstraction: Defines how files and directories are represented within a specific backend.
    • Usage Information (UsageInfo): Provides tracking for available storage capacity, block sizes, and inode counts.
    • File Operations: Implements standard actions such as creating, reading, writing, and deleting files.
    • Error Handling: Provides a standardized way to handle and report file system errors.
    • Metadata Management: Provides metadata used by the VFS to identify and configure the file system.
  8. What are ZenFS backends and how do they work?

    main
    Backends in ZenFS are configuration-driven objects that act as factories for storage implementations. Instead of the Virtual File System (VFS) interacting with storage media directly, the VFS mounts a configured backend. The backend's responsibility is to take a set of configuration options and use its create method to return a FileSystem instance (or a Promise resolving to one). This abstraction allows the VFS to remain decoupled from specific storage implementations.
  9. How ZenFS backends and mounting work

    main

    ZenFS uses a modular system of backends to store and retrieve data. A backend is a storage engine (e.g., InMemory, Zip, IndexedDB).

    To use specific storage, you must mount a backend to a path. You can manage these mounts using the configure or configureSingle functions.

    When configuring a mount point, you can provide:

    1. A Backend object (if it requires no options).
    2. An object containing backend options and a backend property (the Backend object).
    3. An existing FileSystem instance.

    Multiple backends can be mounted simultaneously to different paths, allowing you to treat different storage types (like a Zip file and an IndexedDB instance) as part of a single unified filesystem tree.

  10. Enable built-in device files in ZenFS

    main

    ZenFS supports device files that follow Linux-like behavior. You can automatically enable standard devices (such as /dev/null and /dev/random) by setting the addDevices configuration option to true within the configure function.

    await configure({
    	mounts: {/* ... */},
    	addDevices: true,
    });
    
    // Example usage of enabled devices:
    fs.writeFileSync('/dev/null', 'Some data to be discarded');
    
    const randomData = new Uint8Array(100);
    const random = fs.openSync('/dev/random', 'r');
    fs.readSync(random, randomData);
    fs.closeSync(random);
  11. Best practices for backend type definitions

    main

    When defining backends, ZenFS uses a specific pattern to ensure high-fidelity type errors and usability. If you are building a backend, follow this pattern:

    1. Use as const satisfies Backend<FileSystem, Options> to enable the strictest possible type checking.
    2. Use a type alias for the implementation object.
    3. Export an interface that extends that type. This ensures that if a user misconfigures the backend, the TypeScript error message displays the clear interface name instead of the entire expanded object structure.
    4. Export the implementation object using the interface type.
    const _InMemory = {
    	// ... implementation
    } as const satisfies Backend<StoreFS<InMemoryStore>, { name?: string }>;
    
    type _InMemory = typeof _InMemory;
    export interface InMemory extends _InMemory {}
    export const InMemory: InMemory = _InMemory;