Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 

README.md

@auto-engineer/file-store

Platform-agnostic file storage abstraction with in-memory and Node.js implementations.


Purpose

Without @auto-engineer/file-store, you would have to write platform-specific file operations, handle path normalization across operating systems, and maintain separate test doubles for every component that touches the filesystem.

This package defines a single IFileStore interface that both InMemoryFileStore and NodeFileStore implement. Code that depends on IFileStore can run against the in-memory store in tests and the Node store in production with no changes. All paths are normalized to POSIX format, and all reads return null for missing files instead of throwing.

Key Concepts

  • Binary-first API -- Core operations use Uint8Array for cross-platform compatibility. The Node store adds text convenience methods via IExtendedFileStore.
  • Null over exceptions -- read() and readText() return null for missing files rather than throwing.
  • POSIX normalization -- All paths use forward slashes, even on Windows.
  • Auto directory creation -- write() and writeText() create parent directories automatically.
  • Two entry points -- The main entry point (".") is platform-agnostic; the "./node" entry point requires Node.js built-ins.

Installation

pnpm add @auto-engineer/file-store

Quick Start

import { InMemoryFileStore } from '@auto-engineer/file-store';

const store = new InMemoryFileStore();

await store.write('/data/file.txt', new TextEncoder().encode('content'));
const data = await store.read('/data/file.txt');

console.log(new TextDecoder().decode(data!));
// → "content"

How-to Guides

Use the In-Memory Store for Testing

import { InMemoryFileStore } from '@auto-engineer/file-store';

const store = new InMemoryFileStore();
await store.write('/input.txt', new TextEncoder().encode('data'));

const exists = await store.exists('/input.txt');
const tree = await store.listTree('/');

Use the Node Store for Production

import { NodeFileStore } from '@auto-engineer/file-store/node';

const store = new NodeFileStore();

await store.writeText('config.json', JSON.stringify({ key: 'value' }));
const text = await store.readText('config.json');

List a Directory Tree with Pruning

import { NodeFileStore } from '@auto-engineer/file-store/node';

const store = new NodeFileStore();
const tree = await store.listTree('/project', {
  pruneDirRegex: /node_modules|\.git/,
  includeSizes: true,
});

for (const entry of tree) {
  console.log(`${entry.type} ${entry.path} (${entry.size} bytes)`);
}

Dependency Injection

Accept IFileStore in your constructors so callers can swap implementations:

import type { IFileStore } from '@auto-engineer/file-store';

class DocumentManager {
  constructor(private store: IFileStore) {}

  async save(id: string, content: string): Promise<void> {
    await this.store.write(`/docs/${id}.json`, new TextEncoder().encode(content));
  }
}

API Reference

Package Exports

Entry Point Import Path Exports
Main (.) @auto-engineer/file-store InMemoryFileStore, IFileStore, IExtendedFileStore
Node (./node) @auto-engineer/file-store/node NodeFileStore, IFileStore, IExtendedFileStore
// Platform-agnostic (no Node.js dependency)
import { InMemoryFileStore, type IFileStore, type IExtendedFileStore } from '@auto-engineer/file-store';

// Node.js only
import { NodeFileStore } from '@auto-engineer/file-store/node';

IFileStore Interface

The base interface implemented by both stores.

Method Signature Description
write (path: string, data: Uint8Array) => Promise<void> Write binary data. Creates parent directories.
read (path: string) => Promise<Uint8Array | null> Read binary data. Returns null if missing.
exists (path: string) => Promise<boolean> Check whether a file or directory exists.
listTree (root?: string, opts?: ListTreeOpts) => Promise<TreeEntry[]> Recursively list files and directories.
remove (path: string) => Promise<void> Delete a file.

listTree Options

Option Type Default Description
followSymlinkDirs boolean true Traverse symlinked directories
includeSizes boolean true Include file sizes in results
pruneDirRegex RegExp -- Skip directories whose path matches

listTree Return Type

Array<{ path: string; type: 'file' | 'dir'; size: number }>

IExtendedFileStore Interface

Extends IFileStore with convenience methods. Implemented by NodeFileStore.

Method Signature Description
ensureDir (path: string) => Promise<void> Create a directory recursively (like mkdir -p).
readdir (path: string) => Promise<Array<{ name: string; type: 'file' | 'dir' }>> List immediate children of a directory.
readText (path: string) => Promise<string | null> Read a file as UTF-8 text. Returns null if missing.
writeText (path: string, text: string) => Promise<void> Write a UTF-8 text file. Creates parent directories.
join (...parts: string[]) => string Join path segments, returning a POSIX path. Handles file:// URLs.
dirname (p: string) => string Return the directory portion of a path as a POSIX path.
fromHere (relative: string, base?: string) => string Resolve a relative path from a base directory (defaults to __dirname).

InMemoryFileStore Class

Implements IFileStore. Stores files in a Map<string, Uint8Array>. Paths are normalized to start with /. The remove method deletes the path and any files beneath it (prefix match), so it works for both files and directories.

NodeFileStore Class

Implements IExtendedFileStore. Delegates to node:fs/promises. Paths can be absolute, relative (resolved against process.cwd()), or file:// URLs. All returned paths use POSIX separators.


Architecture

src/
├── index.ts              # Main entry: exports InMemoryFileStore + types
├── node.ts               # Node entry: exports NodeFileStore + types
├── types.ts              # IFileStore and IExtendedFileStore interfaces
├── path.ts               # toPosix() helper
├── InMemoryFileStore.ts  # In-memory implementation (Map-backed)
└── NodeFileStore.ts      # Node.js fs implementation
classDiagram
    class IFileStore {
        <<interface>>
        +write(path, data) Promise~void~
        +read(path) Promise~Uint8Array | null~
        +exists(path) Promise~boolean~
        +listTree(root, opts) Promise~TreeEntry[]~
        +remove(path) Promise~void~
    }
    class IExtendedFileStore {
        <<interface>>
        +ensureDir(path) Promise~void~
        +readdir(path) Promise~DirEntry[]~
        +readText(path) Promise~string | null~
        +writeText(path, text) Promise~void~
        +join(parts) string
        +dirname(p) string
        +fromHere(relative, base) string
    }
    IExtendedFileStore --|> IFileStore
    InMemoryFileStore ..|> IFileStore
    NodeFileStore ..|> IExtendedFileStore

    class InMemoryFileStore {
        -files Map~string, Uint8Array~
    }
    class NodeFileStore {
    }
Loading

Dependencies

This package has zero external dependencies. The Node entry point uses only Node.js built-in modules (node:fs/promises, node:path, node:url).