# @firesystem/core

Virtual File System (VFS) core interfaces and types for JavaScript/TypeScript applications. This package provides a consistent API for implementing file system operations across different storage backends.

## Features

- 🎯 **Unified API** - Same interface for IndexedDB, Memory, S3, or any storage backend
- 📁 **Full FS Operations** - Read, write, mkdir, rmdir, rename, move, copy, and more
- 👀 **Watch System** - Real-time notifications with glob pattern support
- 🔍 **Glob Patterns** - Search files using familiar glob syntax
- 🚀 **Zero Dependencies** - Core package has no runtime dependencies
- 📝 **TypeScript First** - Written in TypeScript with complete type definitions
- ✅ **Battle Tested** - Comprehensive test suite ensuring reliability

## Installation

```bash
npm install @firesystem/core
# or
yarn add @firesystem/core
# or
pnpm add @firesystem/core
```

## Available Implementations

- [`@firesystem/indexeddb`](../indexeddb) - Browser storage using IndexedDB
- [`@firesystem/memory`](../memory) - In-memory storage for testing
- [`@firesystem/s3`](../s3) - AWS S3 storage backend
- [`@firesystem/workspace`](../workspace) - Multi-source workspace manager
- More coming soon (Node.js fs, etc.)

## Creating Custom Implementations

VFS provides a `BaseFileSystem` abstract class that implements common functionality:

```typescript
import { BaseFileSystem } from "@firesystem/core";

export class MyCustomFileSystem extends BaseFileSystem {
  // Define capabilities
  readonly capabilities = {
    readonly: false,
    caseSensitive: true,
    atomicRename: true,
    supportsWatch: true,
    supportsMetadata: true,
    maxFileSize: 10 * 1024 * 1024, // 10MB
    maxPathLength: 255,
  };

  // Implement required methods
  async readFile(path: string): Promise<FileEntry> {
    // Your implementation
  }

  async writeFile(
    path: string,
    content: any,
    metadata?: FileMetadata,
  ): Promise<FileEntry> {
    // Your implementation
  }

  // ... implement other required methods

  // Override permission methods if needed
  async canModify(path: string): Promise<boolean> {
    // Custom permission logic
    return super.canModify(path); // or your own logic
  }
}
```

## API Overview

### IFileSystem Interface

The main interface that all VFS implementations must follow:

```typescript
interface IFileSystem {
  // File operations
  readFile(path: string): Promise<FileEntry>;
  writeFile(
    path: string,
    content: any,
    metadata?: FileMetadata,
  ): Promise<FileEntry>;
  deleteFile(path: string): Promise<void>;
  exists(path: string): Promise<boolean>;

  // Directory operations
  readDir(path: string): Promise<FileEntry[]>;
  mkdir(path: string, recursive?: boolean): Promise<FileEntry>;
  rmdir(path: string, recursive?: boolean): Promise<void>;

  // File system operations
  rename(oldPath: string, newPath: string): Promise<FileEntry>;
  move(sourcePaths: string[], targetPath: string): Promise<void>;
  copy(sourcePath: string, targetPath: string): Promise<FileEntry>;

  // Watch operations
  watch(pattern: string, callback: (event: FSEvent) => void): Disposable;

  // Utility operations
  stat(path: string): Promise<FileStat>;
  glob(pattern: string): Promise<string[]>;

  // Storage management
  clear(): Promise<void>;
  size(): Promise<number>;

  // Permission checking
  canModify(path: string): Promise<boolean>;
  canCreateIn(parentPath: string): Promise<boolean>;

  // Atomic operations (optional)
  writeFileAtomic?(
    path: string,
    content: any,
    metadata?: FileMetadata,
  ): Promise<FileEntry>;

  // Capabilities (optional)
  readonly capabilities?: IFileSystemCapabilities;
}
```

## Usage Examples

### Basic File Operations

```typescript
import { IndexedDBFileSystem } from "@firesystem/indexeddb";

const fs = new IndexedDBFileSystem({ dbName: "my-app" });

// Write a file
await fs.writeFile("/hello.txt", "Hello, World!");

// Read a file
const file = await fs.readFile("/hello.txt");
console.log(file.content); // "Hello, World!"

// Check if file exists
const exists = await fs.exists("/hello.txt"); // true

// Delete a file
await fs.deleteFile("/hello.txt");
```

### Working with Directories

```typescript
// Create a directory
await fs.mkdir("/src");

// Create nested directories
await fs.mkdir("/src/components/Button", true);

// List directory contents
const files = await fs.readDir("/src");
console.log(files); // [{ name: "components", type: "directory", ... }]

// Remove empty directory
await fs.rmdir("/src/components/Button");

// Remove directory and all contents
await fs.rmdir("/src", true);
```

### File Metadata

```typescript
// Write file with metadata
await fs.writeFile(
  "/document.json",
  { title: "My Document" },
  {
    tags: ["important", "work"],
    description: "Quarterly report",
  },
);

// Read file with metadata
const doc = await fs.readFile("/document.json");
console.log(doc.metadata?.tags); // ["important", "work"]

// Get file statistics
const stats = await fs.stat("/document.json");
console.log(stats.size); // File size in bytes
console.log(stats.created); // Date created
console.log(stats.modified); // Date last modified
```

### Moving and Copying Files

```typescript
// Rename a file
await fs.rename("/old-name.txt", "/new-name.txt");

// Move files to a directory
await fs.mkdir("/archive");
await fs.move(["/file1.txt", "/file2.txt"], "/archive");

// Copy a file
await fs.copy("/template.html", "/index.html");
```

### Glob Patterns

```typescript
// Find all JavaScript files
const jsFiles = await fs.glob("**/*.js");

// Find all files in src directory
const srcFiles = await fs.glob("/src/**/*");

// Find all test files
const testFiles = await fs.glob("**/*.test.{js,ts}");

// Find all files in root directory only
const rootFiles = await fs.glob("*");
```

### Watch System

```typescript
// Watch all files
const watcher = fs.watch("**", (event) => {
  console.log(`${event.type}: ${event.path}`);
});

// Watch specific file types
fs.watch("**/*.json", (event) => {
  if (event.type === "updated") {
    console.log(`JSON file updated: ${event.path}`);
  }
});

// Watch specific directory
fs.watch("/src/**/*", (event) => {
  console.log(`Change in src: ${event.type} ${event.path}`);
});

// Stop watching
watcher.dispose();
```

### Watch Event Types

```typescript
type FSEventType =
  | "created" // File or directory was created
  | "updated" // File content was modified
  | "deleted" // File or directory was deleted
  | "moved" // File or directory was moved (event.oldPath available)
  | "renamed"; // File or directory was renamed (event.oldPath available)
```

### Storage Management

```typescript
// Get total storage size
const totalSize = await fs.size();
console.log(`Total storage: ${totalSize} bytes`);

// Clear all files (except root directory)
await fs.clear();
```

### Permission Checking

VFS now supports permission checking before operations:

```typescript
// Check if can modify a file
if (await fs.canModify("/protected.txt")) {
  await fs.deleteFile("/protected.txt");
} else {
  console.log("File is read-only or protected");
}

// Check if can create in directory
if (await fs.canCreateIn("/restricted")) {
  await fs.writeFile("/restricted/new.txt", "content");
} else {
  console.log("Cannot create files in this directory");
}
```

### Atomic Writing

For critical operations, use atomic writing when available:

```typescript
// Writes to temp file then renames (atomic)
// Falls back to regular write if not supported
if (fs.writeFileAtomic) {
  await fs.writeFileAtomic("/important.json", {
    data: "critical data",
  });
} else {
  await fs.writeFile("/important.json", {
    data: "critical data",
  });
}
```

### File System Capabilities

Check what a file system supports:

```typescript
// Check capabilities
if (fs.capabilities) {
  console.log(`Read-only: ${fs.capabilities.readonly}`);
  console.log(`Case sensitive: ${fs.capabilities.caseSensitive}`);
  console.log(`Atomic rename: ${fs.capabilities.atomicRename}`);
  console.log(`Supports watch: ${fs.capabilities.supportsWatch}`);

  if (fs.capabilities.maxFileSize) {
    console.log(`Max file size: ${fs.capabilities.maxFileSize} bytes`);
  }
}

// Example: Check before large file operations
const largeData = new ArrayBuffer(100 * 1024 * 1024); // 100MB
if (
  fs.capabilities?.maxFileSize &&
  largeData.byteLength > fs.capabilities.maxFileSize
) {
  throw new Error(
    `File too large. Max size: ${fs.capabilities.maxFileSize} bytes`,
  );
}
```

## Path Handling

All paths in VFS are normalized automatically:

```typescript
// These all resolve to the same path
await fs.writeFile("file.txt", "content"); // -> /file.txt
await fs.writeFile("/file.txt", "content"); // -> /file.txt
await fs.writeFile("//file.txt", "content"); // -> /file.txt
await fs.writeFile("/file.txt/", "content"); // -> /file.txt
```

## Error Handling

VFS follows POSIX error conventions:

```typescript
try {
  await fs.readFile("/missing.txt");
} catch (error) {
  // Error: ENOENT: no such file or directory, open '/missing.txt'
}

try {
  await fs.rmdir("/has-files");
} catch (error) {
  // Error: ENOTEMPTY: directory not empty, rmdir '/has-files'
}

try {
  await fs.mkdir("/already/exists");
} catch (error) {
  // Error: EEXIST: file already exists, mkdir '/already/exists'
}
```

## Type Definitions

### FileEntry

```typescript
interface FileEntry {
  path: string;
  name: string;
  type: "file" | "directory";
  size?: number;
  created?: Date;
  modified?: Date;
  metadata?: FileMetadata;
  content?: any; // Only present when reading files
}
```

### FileStat

```typescript
interface FileStat {
  path: string;
  size: number;
  type: "file" | "directory";
  created: Date;
  modified: Date;
  accessed?: Date;
  readonly?: boolean; // Indicates if file/directory is read-only
}
```

### FileMetadata

```typescript
interface FileMetadata {
  tags?: string[];
  description?: string;
  [key: string]: any; // Custom metadata
}
```

### FSEvent

```typescript
interface FSEvent {
  type: FSEventType;
  path: string;
  oldPath?: string; // Present for move/rename events
  timestamp: Date;
  metadata?: Record<string, any>;
}
```

### Disposable

```typescript
interface Disposable {
  dispose(): void;
}
```

### IFileSystemCapabilities

```typescript
interface IFileSystemCapabilities {
  // Operation support
  readonly: boolean; // Is the file system read-only?
  caseSensitive: boolean; // Does it differentiate case?
  atomicRename: boolean; // Is rename operation atomic?

  // Limits
  maxFileSize?: number; // Maximum file size in bytes
  maxPathLength?: number; // Maximum path length

  // Features
  supportsWatch: boolean; // Does it support file watching?
  supportsMetadata: boolean; // Does it support custom metadata?
}
```

## Glob Pattern Support

VFS supports standard glob patterns:

- `*` - Matches any characters except `/`
- `**` - Matches any number of directories
- `?` - Matches single character except `/`
- `{a,b}` - Matches either `a` or `b`
- `[abc]` - Matches any character in brackets
- `[!abc]` - Matches any character not in brackets

### Examples

```typescript
"*.js"; // All .js files in root
"**/*.js"; // All .js files recursively
"src/**/*.ts"; // All .ts files under src
"*.{js,ts}"; // All .js and .ts files in root
"test/**"; // Everything under test directory
"**/test/*"; // All files directly under any test directory
```

## Best Practices

### 1. Always use absolute paths internally

```typescript
// Good
await fs.writeFile("/config/app.json", config);

// Okay (will be normalized to /config/app.json)
await fs.writeFile("config/app.json", config);
```

### 2. Check existence before operations

```typescript
if (await fs.exists("/old-file.txt")) {
  await fs.deleteFile("/old-file.txt");
}
```

### 3. Use recursive flag for nested directories

```typescript
// This will fail if parent doesn't exist
await fs.mkdir("/deep/nested/dir");

// This will create all parent directories
await fs.mkdir("/deep/nested/dir", true);
```

### 4. Dispose watchers when done

```typescript
const watcher = fs.watch("**/*.log", handler);

// Later...
watcher.dispose(); // Stop watching to free resources
```

### 5. Handle errors appropriately

```typescript
async function safeReadFile(path: string): Promise<string | null> {
  try {
    const file = await fs.readFile(path);
    return file.content;
  } catch (error) {
    if (error.message.includes("ENOENT")) {
      return null; // File doesn't exist
    }
    throw error; // Re-throw other errors
  }
}
```

## Testing

For testing, use the in-memory implementation:

```typescript
import { MemoryFileSystem } from "@firesystem/memory";

describe("My App", () => {
  let fs: IFileSystem;

  beforeEach(() => {
    fs = new MemoryFileSystem();
  });

  it("should save user data", async () => {
    await saveUserData(fs, { name: "John" });

    const file = await fs.readFile("/users/john.json");
    expect(file.content.name).toBe("John");
  });
});
```

## Contributing

Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.

## License

MIT © Anderson D. Rosa
