or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-mem-fs

Simple in-memory vinyl file store with lazy loading, streaming operations, and pipeline transformations.

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/mem-fs@4.1.x

To install, run

npx @tessl/cli install tessl/npm-mem-fs@4.1.0

index.mddocs/

mem-fs

mem-fs is a TypeScript library that provides an in-memory vinyl file store with lazy loading capabilities from disk, streaming operations, and pipeline transformations. It enables developers to work with files in memory while maintaining the flexibility to load from and persist to the file system on demand, making it ideal for build tools, static site generators, and file processing pipelines.

Package Information

  • Package Name: mem-fs
  • Package Type: npm
  • Language: TypeScript
  • Installation: npm install mem-fs

Core Imports

import { create, Store } from "mem-fs";

For CommonJS:

const { create, Store } = require("mem-fs");

Basic Usage

import { create } from "mem-fs";
import File from "vinyl";

// Create a store instance
const store = create();

// Load a file from disk (lazy loaded)
const file = store.get("./package.json");
console.log(file.contents?.toString());

// Add/modify files in memory
const newFile = new File({
  path: "./build/output.txt",
  contents: Buffer.from("Generated content")
});
store.add(newFile);

// Stream all files through processing pipeline
await store.pipeline(
  // Transform files through processing steps
  transform({ objectMode: true }, function(file, encoding, callback) {
    // Process each file
    file.contents = Buffer.from(file.contents.toString().toUpperCase());
    callback(null, file);
  })
);

Architecture

mem-fs is built around several key components:

  • Store Class: Main file storage interface with generic type support for custom file objects
  • Lazy Loading: Files loaded from disk only when first accessed via get() method
  • Event System: Emits 'change' events when files are added or modified
  • Streaming Interface: Convert store contents to readable streams with optional filtering
  • Pipeline Processing: Transform files through async processing pipelines with conflict resolution
  • Vinyl Integration: Built on vinyl file format for compatibility with build tools and task runners

Capabilities

Store Creation

Factory function to create new Store instances with optional custom file loaders.

/**
 * Creates a new Store instance with default vinyl file loading
 * @returns New Store instance
 */
function create<StoreFile extends { path: string } = File>(): Store<StoreFile>;

File Storage and Retrieval

Core file storage class with in-memory caching and lazy loading from disk.

/**
 * Main file store class with generic type support
 */
class Store<StoreFile extends { path: string } = File> extends EventEmitter {
  /**
   * Custom file loading function
   */
  loadFile: (filepath: string) => StoreFile;

  /**
   * Create a new Store instance
   * @param options - Configuration options
   */
  constructor(options?: { loadFile?: (filepath: string) => StoreFile });

  /**
   * Get file from memory or load from disk if not cached
   * @param filepath - File path to retrieve
   * @returns File object (empty vinyl file if not found)
   */
  get(filepath: string): StoreFile;

  /**
   * Check if file exists in memory without loading from disk
   * @param filepath - File path to check
   * @returns True if file exists in memory
   */
  existsInMemory(filepath: string): boolean;

  /**
   * Add or update file in store and emit change event
   * @param file - File object to add
   * @returns Store instance for chaining
   */
  add(file: StoreFile): this;

  /**
   * Iterate over all files in memory
   * @param onEach - Callback function for each file
   * @returns Store instance for chaining
   */
  each(onEach: (file: StoreFile) => void): this;

  /**
   * Get array of all files currently in memory
   * @returns Array of all stored files
   */
  all(): StoreFile[];

  /**
   * Create readable stream of files with optional filtering
   * @param options - Stream configuration options
   * @returns Readable stream of files
   */
  stream(options?: StreamOptions<StoreFile>): Readable;

  /**
   * Process files through transformation pipeline
   * @param options - Pipeline configuration options or first transform
   * @param transforms - Additional transformation streams
   * @returns Promise that resolves when pipeline completes
   */
  pipeline(
    options?: PipelineOptions<StoreFile> | FileTransform<StoreFile>,
    ...transforms: FileTransform<StoreFile>[]
  ): Promise<void>;
}

File Loading

Default file loader using vinyl-file with fallback for missing files.

/**
 * Default file loader using vinyl-file, creates empty vinyl file if loading fails
 * @param filepath - Path to file to load
 * @returns Vinyl file object
 */
function loadFile(filepath: string): File;

Type Utilities

Helper function for type checking and configuration interfaces.

/**
 * Type guard to check if value is a FileTransform
 * @param transform - Value to check
 * @returns True if value is a FileTransform
 */
function isFileTransform<StoreFile extends { path: string } = File>(
  transform: PipelineOptions<StoreFile> | FileTransform<StoreFile> | undefined
): transform is FileTransform<StoreFile>;

Types

/**
 * Stream transform type for file processing pipelines
 */
type FileTransform<File> = PipelineTransform<PipelineTransform<any, File>, File>;

/**
 * Options for stream() method
 */
interface StreamOptions<StoreFile extends { path: string } = File> {
  /** Optional file filter predicate */
  filter?: (file: StoreFile) => boolean;
}

/**
 * Options for pipeline() method
 */
interface PipelineOptions<StoreFile extends { path: string } = File> {
  /** Optional file filter predicate */
  filter?: (file: StoreFile) => boolean;
  /** Conflict resolution strategy for duplicate files */
  resolveConflict?: (current: StoreFile, newFile: StoreFile) => StoreFile;
  /** Whether to create new store map (default: true) */
  refresh?: boolean;
  /** Allow overriding duplicate files (alternative to resolveConflict) */
  allowOverride?: boolean;
}

Events

The Store class extends EventEmitter and emits the following events:

/**
 * Emitted when files are added or modified
 * @event change
 * @param filepath - Path of the changed file
 */
store.on('change', (filepath: string) => void);

Usage Examples

Basic File Operations

import { create } from "mem-fs";
import File from "vinyl";

const store = create();

// Load existing file from disk
const packageFile = store.get("./package.json");
console.log(packageFile.contents?.toString());

// Check if file is already in memory
if (!store.existsInMemory("./README.md")) {
  console.log("README.md will be loaded from disk");
}

// Create and add new file
const outputFile = new File({
  path: "./dist/bundle.js",
  contents: Buffer.from("console.log('Hello world');")
});
store.add(outputFile);

// Listen for file changes
store.on('change', (filepath) => {
  console.log(`File changed: ${filepath}`);
});

Iterating Over Files

// Iterate over all files
store.each((file) => {
  console.log(`Processing: ${file.path}`);
  // Modify file contents
  if (file.path.endsWith('.js')) {
    file.contents = Buffer.from(`// Generated\n${file.contents?.toString()}`);
  }
});

// Get all files as array
const allFiles = store.all();
console.log(`Total files: ${allFiles.length}`);

Streaming Operations

import { Transform } from "stream";

// Create filtered stream
const jsFiles = store.stream({
  filter: (file) => file.path.endsWith('.js')
});

jsFiles.on('data', (file) => {
  console.log(`JavaScript file: ${file.path}`);
});

// Process files through pipeline
await store.pipeline(
  { filter: (file) => file.path.endsWith('.md') },
  new Transform({
    objectMode: true,
    transform(file, encoding, callback) {
      // Convert markdown files to uppercase
      file.contents = Buffer.from(file.contents?.toString().toUpperCase() || '');
      callback(null, file);
    }
  })
);

Advanced Pipeline Usage

import { Duplex } from "stream";

// Complex pipeline with conflict resolution
await store.pipeline(
  {
    filter: (file) => file.path.includes('src/'),
    resolveConflict: (current, newFile) => {
      // Keep the newer file based on modification time
      return newFile.stat?.mtime > current.stat?.mtime ? newFile : current;
    }
  },
  // Transform stream that renames files
  Duplex.from(async function* (files) {
    for await (const file of files) {
      file.path = file.path.replace('src/', 'build/');
      yield file;
    }
  }),
  // Minification transform
  new Transform({
    objectMode: true,
    transform(file, encoding, callback) {
      if (file.path.endsWith('.js')) {
        // Simulate minification
        const content = file.contents?.toString().replace(/\s+/g, ' ') || '';
        file.contents = Buffer.from(content);
      }
      callback(null, file);
    }
  })
);

Custom File Loader

// Create store with custom file loader
const customStore = new Store({
  loadFile: (filepath) => {
    return new File({
      path: filepath,
      contents: Buffer.from(`// Custom loaded: ${filepath}`)
    });
  }
});

const customFile = customStore.get('./any-file.js');
console.log(customFile.contents?.toString()); // "// Custom loaded: ./any-file.js"

Error Handling

  • Missing Files: get() returns empty vinyl files with contents: null for non-existent or unreadable files
  • Duplicate Files: pipeline() throws errors for duplicate files unless conflict resolution is configured via resolveConflict or allowOverride
  • Invalid Paths: All file paths are resolved using path.resolve() for consistency
  • Custom Loaders: File loading errors in custom loadFile functions should be handled by the implementer