@workspace-fs/core
Version:
Multi-project workspace manager for Firesystem with support for multiple sources
1,291 lines (1,023 loc) โข 40 kB
Markdown
# @firesystem/workspace
**Revolutionary Multi-Project Workspace Manager** - The first file system workspace that enables **multiple projects running simultaneously in parallel**, breaking the traditional "one project = one filesystem" paradigm. Unlike IDEs that switch between projects, Firesystem Workspace keeps ALL projects active and accessible at once.
[](https://badge.fury.io/js/@firesystem%2Fworkspace)
[](https://www.typescriptlang.org/)
[](https://opensource.org/licenses/MIT)
[](/)
## ๐ Why This Changes Everything
Traditional development environments work with **one active project at a time**:
```typescript
// โ Traditional approach (VSCode, etc.)
await closeProject("frontend"); // Lost from memory
await openProject("backend"); // Only this is accessible
await closeProject("backend"); // Lost from memory
await openProject("database"); // Only this is accessible
```
**Firesystem Workspace revolutionizes this:**
```typescript
// โ
Revolutionary approach - ALL PROJECTS ACTIVE SIMULTANEOUSLY
const frontend = workspace.getProject("frontend"); // Always accessible
const backend = workspace.getProject("backend"); // Always accessible
const database = workspace.getProject("database"); // Always accessible
// Cross-project operations without switching context
const config = await backend.fs.readFile("/config/database.json");
await database.fs.writeFile("/import/backend-config.json", config);
await frontend.fs.writeFile("/src/api-config.json", config);
// One project focused for UI, but ALL remain active
workspace.setActiveProject("frontend"); // Just changes UI focus
```
## ๐ Revolutionary Features
### ๐ฅ **Multi-Project Parallelism**
- **Multiple file systems active simultaneously** - not just "open tabs"
- **Cross-project operations** - copy/sync data between different storage types
- **Zero context switching** - all projects remain in memory and accessible
- **Focus vs Active** - UI shows one project, but all remain operational
### ๐งฉ **Provider-Agnostic Architecture**
- **Workspace knows NOTHING about specific implementations**
- **Completely extensible** - anyone can create providers for any storage
- **Zero dependencies** - workspace has no file system implementations
- **Capability-aware** - each provider declares what it can/cannot do
### โก **Intelligent Resource Management**
- **Hot enable/disable** - pause projects for performance without losing config
- **Automatic cleanup** - orphaned references removed on startup
- **Memory optimization** - smart resource management for hundreds of projects
- **Persistent state** - everything survives restarts
### ๐๏ธ **Advanced Project Lifecycle**
- **Complete deletion support** - remove projects with optional data cleanup
- **Provider-aware deletion** - knows which providers store local vs remote data
- **Confirmation events** - intercept and cancel deletions programmatically
- **Storage estimation** - see how much space will be freed before deleting
## ๐ฆ Installation
```bash
npm install @firesystem/workspace @firesystem/core
# or
yarn add @firesystem/workspace @firesystem/core
# or
pnpm add @firesystem/workspace @firesystem/core
```
## ๐ Quick Start
### Basic Multi-Project Setup
```typescript
import { WorkspaceFileSystem } from "@firesystem/workspace";
import { memoryProvider } from "@firesystem/memory/provider";
import { indexedDBProvider } from "@firesystem/indexeddb/provider";
// Create workspace and register providers
const workspace = new WorkspaceFileSystem();
workspace.registerProvider(memoryProvider);
workspace.registerProvider(indexedDBProvider);
// Initialize (restores previous state automatically)
await workspace.initialize();
// Load multiple projects simultaneously
const devProject = await workspace.loadProject({
id: "dev-env",
name: "Development Environment",
source: {
type: "memory",
config: {
initialData: {
"/src/app.ts": { content: "console.log('dev');" },
"/config/dev.json": { content: JSON.stringify({ env: "development" }) }
}
}
}
});
const prodBackup = await workspace.loadProject({
id: "prod-backup",
name: "Production Backup",
source: {
type: "indexeddb",
config: { dbName: "production-backup" }
}
});
// ALL PROJECTS ARE NOW ACTIVE AND ACCESSIBLE!
console.log("Active projects:", workspace.getProjects().length); // 2
// Cross-project operations (the revolutionary part!)
const prodConfig = await prodBackup.fs.readFile("/config/production.json");
await devProject.fs.writeFile("/config/prod-copy.json", prodConfig.content);
// Work with the focused project transparently
workspace.setActiveProject("dev-env");
await workspace.writeFile("/test.js", "// This goes to dev-env");
// But other projects remain accessible
await prodBackup.fs.writeFile("/logs/import.log", "Config copied to dev");
```
## ๐ฏ Core Concepts
### 1. **Multi-Project Parallelism**
Unlike traditional workspaces that handle one project at a time:
```typescript
// Traditional: Only one project accessible
const currentProject = getCurrentProject(); // Only this exists
// Firesystem: All projects accessible simultaneously
const projects = workspace.getProjects(); // All active projects
const dev = workspace.getProject("dev");
const staging = workspace.getProject("staging");
const prod = workspace.getProject("prod");
// Work with all at once
const devConfig = await dev.fs.readFile("/config.json");
const stagingData = await staging.fs.glob("**/*.log");
await prod.fs.writeFile("/backup/dev-config.json", devConfig.content);
```
### 2. **Focus vs Active States**
- **Active Projects**: Loaded in memory, consuming resources, ready for operations
- **Focused Project**: The project shown in UI (File Explorer), receives direct operations
- **Disabled Projects**: Configuration preserved but not loaded, zero resource usage
```typescript
// Load multiple projects - all become active
await workspace.loadProject({ id: "app", source: { type: "memory" } });
await workspace.loadProject({ id: "docs", source: { type: "indexeddb" } });
await workspace.loadProject({ id: "assets", source: { type: "s3" } });
// Set focus for UI - others remain active
await workspace.setActiveProject("app"); // File Explorer shows "app"
// Direct access to any project
const docs = workspace.getProject("docs");
const assets = workspace.getProject("assets");
// Proxy operations go to focused project
await workspace.writeFile("/test.txt", "content"); // Goes to "app"
// Direct operations go to specific project
await docs.fs.writeFile("/readme.md", "# Documentation");
await assets.fs.writeFile("/logo.png", imageData);
```
### 3. **Performance Optimization**
```typescript
// Disable projects for performance (keeps config)
await workspace.disableProject("large-dataset"); // Frees memory, keeps config
// Re-enable when needed
await workspace.enableProject("large-dataset"); // Reconstructs from config
// Batch operations
await workspace.disableProjects(["old-1", "old-2", "old-3"]);
await workspace.enableProjects(["needed-1", "needed-2"]);
// Auto-optimization settings
const workspace = new WorkspaceFileSystem({
settings: {
maxActiveProjects: 5, // Auto-disable least recently used
autoDisableAfter: 30 * 60 * 1000, // 30 minutes of inactivity
keepFocusedActive: true // Never auto-disable focused project
}
});
```
### 4. **Project Deletion**
Delete projects with full control over data cleanup:
```typescript
// Delete project configuration only (preserves data)
await workspace.deleteProject("temp-project");
// Delete project AND its data
await workspace.deleteProject("old-project", {
deleteData: true
});
// With confirmation event
workspace.events.on("project:delete-confirm", (event) => {
if (!confirm(`Delete ${event.project.name}?`)) {
event.cancelled = true; // Cancel deletion
}
});
// Skip confirmation for automation
await workspace.deleteProject("temp-project", {
skipConfirmation: true,
deleteData: true
});
```
## ๐ Provider System
The workspace is **completely agnostic** about file systems. Providers encapsulate all implementation knowledge:
### Available Providers
| Provider | Package | Type | Description |
|----------|---------|------|-------------|
| Memory | `@firesystem/memory/provider` | `"memory"` | In-memory, fast iteration |
| IndexedDB | `@firesystem/indexeddb/provider` | `"indexeddb"` | Browser persistent storage |
| S3* | `@firesystem/s3/provider` | `"s3"` | AWS S3 cloud storage |
| GitHub* | `@firesystem/github/provider` | `"github"` | GitHub repository |
| API* | `@firesystem/api/provider` | `"api"` | REST API backend |
*Coming soon
### Registering Providers
```typescript
import { WorkspaceFileSystem } from "@firesystem/workspace";
import { memoryProvider } from "@firesystem/memory/provider";
import { indexedDBProvider } from "@firesystem/indexeddb/provider";
const workspace = new WorkspaceFileSystem();
// Register only what you need
workspace.registerProvider(memoryProvider);
workspace.registerProvider(indexedDBProvider);
// Provider capabilities are automatically detected
const memoryCapabilities = workspace.getProvider("memory")?.getCapabilities();
console.log(memoryCapabilities.supportsWatch); // true
console.log(memoryCapabilities.readonly); // false
```
### Creating Custom Providers
```typescript
import type { SourceProvider } from "@firesystem/workspace";
import { withEvents } from "@firesystem/core";
class DropboxProvider implements SourceProvider {
readonly scheme = "dropbox";
readonly displayName = "Dropbox Storage";
async createFileSystem(config: DropboxConfig) {
const baseFs = new DropboxFileSystem(config);
return withEvents(baseFs); // Add reactive events
}
getCapabilities() {
return {
readonly: false,
caseSensitive: false,
atomicRename: true,
supportsWatch: false, // Dropbox limitation
supportsMetadata: true,
maxFileSize: 150 * 1024 * 1024 // 150MB
};
}
async validateConfiguration(config: any) {
const errors = [];
if (!config.accessToken) errors.push("Access token required");
return { valid: errors.length === 0, errors };
}
// Optional: Data management methods
deleteProjectData?(config: DropboxConfig): Promise<void> {
// Remove Dropbox app folder if needed
}
hasLocalData(): boolean {
return false; // Dropbox is cloud storage
}
async estimateDataSize(config: DropboxConfig): Promise<number | null> {
// Could query Dropbox API for folder size
return null;
}
}
// Register and use
workspace.registerProvider(new DropboxProvider());
await workspace.loadProject({
id: "my-dropbox",
name: "Dropbox Files",
source: {
type: "dropbox",
config: { accessToken: "...", path: "/projects" }
}
});
```
## ๐ Cross-Project Operations
The revolutionary feature that enables true multi-project workflows:
### Copy Files Between Projects
```typescript
// Load different storage types
const memory = await workspace.loadProject({
id: "temp",
source: { type: "memory", config: {} }
});
const persistent = await workspace.loadProject({
id: "storage",
source: { type: "indexeddb", config: { dbName: "app-data" } }
});
// Copy files between different storage types
await workspace.copyFiles(
"temp", // Source project
"/work/*.json", // Source pattern
"storage", // Target project
"/backup/" // Target directory
);
```
### Sync Projects
```typescript
// Sync entire project from S3 to local IndexedDB
await workspace.syncProjects("s3-backup", "local-cache", {
direction: "source-to-target",
deleteOrphaned: false,
skipExisting: true
});
```
### Compare Projects
```typescript
const diff = await workspace.compareProjects("dev", "staging");
console.log(diff.added); // Files in staging not in dev
console.log(diff.removed); // Files in dev not in staging
console.log(diff.modified); // Files that differ
```
### Real-Time Data Pipeline
```typescript
// Set up multi-stage data pipeline
const rawData = await workspace.loadProject({
id: "s3-raw",
source: { type: "s3", config: { bucket: "raw-data" } }
});
const processing = await workspace.loadProject({
id: "memory-work",
source: { type: "memory", config: {} }
});
const results = await workspace.loadProject({
id: "indexeddb-cache",
source: { type: "indexeddb", config: { dbName: "results" } }
});
// Process: S3 โ Memory โ IndexedDB
const files = await rawData.fs.glob("batch-*.json");
for (const file of files) {
// 1. Read from S3
const data = await rawData.fs.readFile(file);
// 2. Process in memory (fast)
const processed = await processData(JSON.parse(data.content));
await processing.fs.writeFile(`/work${file}`, JSON.stringify(processed));
// 3. Save results to IndexedDB (persistent)
await results.fs.writeFile(`/final${file}`, JSON.stringify(processed));
}
```
## ๐ก Event System
Unified events across all projects with rich context:
```typescript
// Project lifecycle events
workspace.events.on("project:loaded", ({ project }) => {
console.log(`โ
${project.name} loaded (${project.source.type})`);
});
workspace.events.on("project:activated", ({ projectId, previousId }) => {
console.log(`๐ Switched from ${previousId} to ${projectId}`);
});
// File operations with project context
workspace.events.on("project:file:written", ({ projectId, path, size }) => {
console.log(`๐ ${path} written in ${projectId} (${size} bytes)`);
});
// Cross-project operations
workspace.events.on("workspace:sync:completed", ({ sourceId, targetId, stats }) => {
console.log(`๐ Synced ${stats.files} files from ${sourceId} to ${targetId}`);
});
// Performance events with enhanced data
workspace.events.on("project:disabled", ({ projectId, hasLocalData, reason }) => {
console.log(`๐ค ${projectId} disabled`);
console.log(` Has local data: ${hasLocalData}`);
console.log(` Reason: ${reason}`);
});
workspace.events.on("project:enabled", ({ projectId }) => {
console.log(`โก ${projectId} re-enabled`);
});
// Deletion events
workspace.events.on("project:deleted", ({ projectId, deletedData }) => {
console.log(`๐๏ธ ${projectId} deleted${deletedData ? ' with data' : ''}`);
});
```
## ๐พ Persistent State & Auto-Recovery
### Automatic State Management
```typescript
const workspace = new WorkspaceFileSystem();
await workspace.initialize(); // Automatically restores:
// - All previously loaded projects
// - Active project selection
// - Project configurations
// - Settings and preferences
// State is automatically saved when:
// - Projects are loaded/unloaded
// - Active project changes
// - Settings are modified
// - Projects are enabled/disabled
```
### Smart Cleanup (v1.0.3+)
The workspace automatically handles corrupted state:
```typescript
// Before v1.0.3: Could crash with "NotFoundError"
// v1.0.3+: Automatically cleans up orphaned references
await workspace.initialize(); // Always succeeds
// Console output:
// โ ๏ธ Removing orphaned activeProjectId: deleted-project-123
// โ ๏ธ Removing orphaned recentProjectId: missing-project-456
// โน๏ธ Workspace state cleaned: removed orphaned project references
```
### Project Discovery
```typescript
// Discover existing IndexedDB projects
const discovered = await workspace.discoverIndexedDBProjects();
console.log("Found projects:", discovered.map(p => p.name));
// Load discovered projects
for (const project of discovered) {
await workspace.loadProject(project);
}
// Get all disabled projects (async now)
const disabledProjects = await workspace.getDisabledProjects();
console.log(`${disabledProjects.length} projects are disabled`);
```
## ๐ Security & Credentials
### Environment-Based Credentials
```bash
# S3 credentials
FIRESYSTEM_S3_ACCESS_KEY_ID=your_access_key
FIRESYSTEM_S3_SECRET_ACCESS_KEY=your_secret_key
FIRESYSTEM_S3_REGION=us-east-1
# GitHub token
FIRESYSTEM_GITHUB_TOKEN=ghp_xxxxxxxxxxxx
# API credentials
FIRESYSTEM_API_API_KEY=your_api_key
```
### Interactive Credential Management
```typescript
// Credentials requested when needed
const s3Project = await workspace.loadProject({
id: "cloud-files",
source: {
type: "s3",
config: { bucket: "my-bucket" }
// No credentials in config - resolved from environment
}
});
// Custom credential providers
workspace.registerCredentialProvider("s3", new InteractiveCredentialProvider({
prompt: async (message, secure) => {
return await showCredentialDialog(message, secure);
}
}));
```
### Export Security
```typescript
// Exports are automatically sanitized
const exportData = await workspace.exportWorkspace({
includeFiles: true,
includeCredentials: false // Never exports credentials
});
// Safe for sharing/version control
await workspace.exportToGitHubGist({
token: "ghp_...",
description: "Team Workspace Configuration",
public: false
});
```
## โ๏ธ Framework Integration
### React + Zustand
```typescript
import { create } from "zustand";
import { WorkspaceFileSystem } from "@firesystem/workspace";
interface WorkspaceStore {
workspace: WorkspaceFileSystem | null;
projects: Project[];
activeProject: Project | null;
initWorkspace: () => Promise<void>;
createProject: (name: string, type: string) => Promise<void>;
switchProject: (projectId: string) => Promise<void>;
deleteProject: (projectId: string, deleteData?: boolean) => Promise<void>;
}
const useWorkspaceStore = create<WorkspaceStore>((set, get) => ({
workspace: null,
projects: [],
activeProject: null,
async initWorkspace() {
const workspace = new WorkspaceFileSystem();
await workspace.initialize(); // Auto-restores state
// Setup reactive events
workspace.events.on("project:loaded", ({ project }) => {
set(state => ({
projects: [...state.projects, project]
}));
});
workspace.events.on("project:activated", ({ projectId }) => {
const project = workspace.getProject(projectId);
set({ activeProject: project });
});
workspace.events.on("project:deleted", ({ projectId }) => {
set(state => ({
projects: state.projects.filter(p => p.id !== projectId),
activeProject: state.activeProject?.id === projectId ? null : state.activeProject
}));
});
set({
workspace,
projects: workspace.getProjects(),
activeProject: workspace.getActiveProject()
});
},
async createProject(name: string, type: string) {
const { workspace } = get();
if (!workspace) return;
const project = await workspace.loadProject({
id: `project-${Date.now()}`,
name,
source: { type, config: {} }
});
await workspace.setActiveProject(project.id);
},
async switchProject(projectId: string) {
const { workspace } = get();
if (!workspace) return;
await workspace.setActiveProject(projectId);
},
async deleteProject(projectId: string, deleteData = false) {
const { workspace } = get();
if (!workspace) return;
await workspace.deleteProject(projectId, { deleteData });
}
}));
// React component
function MultiProjectIDE() {
const {
workspace,
projects,
activeProject,
initWorkspace,
createProject,
switchProject,
deleteProject
} = useWorkspaceStore();
useEffect(() => {
initWorkspace();
}, []);
return (
<div className="ide-container">
{/* Project tabs - all remain active */}
<div className="project-tabs">
{projects.map(project => (
<button
key={project.id}
onClick={() => switchProject(project.id)}
className={activeProject?.id === project.id ? 'active' : ''}
>
{project.name}
<span className="type">({project.source.type})</span>
</button>
))}
</div>
{/* File explorer for active project */}
{activeProject && (
<FileExplorer project={activeProject} />
)}
{/* Cross-project operations */}
<CrossProjectPanel
projects={projects}
onCopyFiles={(source, target, pattern) =>
workspace?.copyFiles(source, pattern, target, "/")
}
onSyncProjects={(source, target) =>
workspace?.syncProjects(source, target)
}
/>
</div>
);
}
```
### Vue 3 Composition API
```typescript
import { ref, reactive, onMounted } from 'vue';
import { WorkspaceFileSystem } from '@firesystem/workspace';
export function useWorkspace() {
const workspace = ref<WorkspaceFileSystem | null>(null);
const projects = ref<Project[]>([]);
const activeProject = ref<Project | null>(null);
const initWorkspace = async () => {
workspace.value = new WorkspaceFileSystem();
await workspace.value.initialize();
workspace.value.events.on('project:loaded', ({ project }) => {
projects.value.push(project);
});
workspace.value.events.on('project:activated', ({ projectId }) => {
activeProject.value = workspace.value!.getProject(projectId);
});
workspace.value.events.on('project:deleted', ({ projectId }) => {
projects.value = projects.value.filter(p => p.id !== projectId);
if (activeProject.value?.id === projectId) {
activeProject.value = null;
}
});
projects.value = workspace.value.getProjects();
activeProject.value = workspace.value.getActiveProject();
};
const createProject = async (name: string, type: string) => {
if (!workspace.value) return;
const project = await workspace.value.loadProject({
id: `project-${Date.now()}`,
name,
source: { type, config: {} }
});
await workspace.value.setActiveProject(project.id);
};
const deleteProject = async (projectId: string, deleteData = false) => {
if (!workspace.value) return;
await workspace.value.deleteProject(projectId, { deleteData });
};
return {
workspace,
projects,
activeProject,
initWorkspace,
createProject,
deleteProject
};
}
```
## ๐ Real-World Use Cases
### 1. Multi-Environment Development
```typescript
class DevelopmentWorkspace {
async setup() {
// Load all environments simultaneously
const local = await workspace.loadProject({
id: "local-dev",
source: { type: "memory", config: {} }
});
const staging = await workspace.loadProject({
id: "staging-mirror",
source: { type: "indexeddb", config: { dbName: "staging-cache" } }
});
const production = await workspace.loadProject({
id: "prod-readonly",
source: { type: "s3", config: { bucket: "prod-backup" } }
});
// Copy production config to development
const prodConfig = await production.fs.readFile("/config/app.json");
await local.fs.writeFile("/config/prod-like.json", prodConfig.content);
// Sync staging data for testing
await workspace.syncProjects("staging-mirror", "local-dev", {
include: ["data/**/*.json"],
exclude: ["data/sensitive/**"]
});
}
}
```
### 2. Content Management Pipeline
```typescript
class ContentPipeline {
async processContent() {
// Multiple content sources active simultaneously
const cms = await workspace.loadProject({
id: "cms-api",
source: { type: "api", config: { baseUrl: "https://cms.example.com" } }
});
const assets = await workspace.loadProject({
id: "cdn-assets",
source: { type: "s3", config: { bucket: "cdn-assets" } }
});
const cache = await workspace.loadProject({
id: "local-cache",
source: { type: "indexeddb", config: { dbName: "content-cache" } }
});
// Process content across all sources
const articles = await cms.fs.glob("/articles/*.json");
for (const article of articles) {
const content = await cms.fs.readFile(article);
const processed = await this.processArticle(content);
// Cache processed content
await cache.fs.writeFile(article, processed);
// Upload assets
if (processed.images) {
for (const image of processed.images) {
await assets.fs.writeFile(`/images/${image.name}`, image.data);
}
}
}
}
}
```
### 3. Data Analysis Workspace
```typescript
class DataAnalysisWorkspace {
async analyzeData() {
// Load datasets from different sources
const rawData = await workspace.loadProject({
id: "data-lake",
source: { type: "s3", config: { bucket: "analytics-data" } }
});
const processing = await workspace.loadProject({
id: "analysis-memory",
source: { type: "memory", config: {} }
});
const results = await workspace.loadProject({
id: "results-db",
source: { type: "indexeddb", config: { dbName: "analysis-results" } }
});
// Cross-dataset analysis
const datasets = await rawData.fs.glob("datasets/**/*.csv");
const analysis = {
totalRows: 0,
correlations: {},
summaries: {}
};
// Process each dataset in memory for speed
for (const dataset of datasets) {
const data = await rawData.fs.readFile(dataset);
const processed = await this.analyzeDataset(data.content);
// Store intermediate results in memory
await processing.fs.writeFile(`/processed/${dataset}`, processed);
// Aggregate analysis
analysis.totalRows += processed.rows;
analysis.summaries[dataset] = processed.summary;
}
// Save final results
await results.fs.writeFile("/analysis/summary.json", JSON.stringify(analysis));
}
}
```
## ๐ Performance & Best Practices
### Memory Management
```typescript
// Configure auto-optimization
const workspace = new WorkspaceFileSystem({
settings: {
maxActiveProjects: 5, // Auto-disable oldest
autoDisableAfter: 30 * 60 * 1000, // 30 minutes inactive
keepFocusedActive: true, // Never disable focused
memoryThreshold: 500 * 1024 * 1024 // 500MB threshold
}
});
// Manual optimization
const stats = await workspace.getProjectStats();
console.log(stats);
// {
// total: 15,
// active: 5,
// disabled: 10,
// memoryUsage: "245MB",
// connections: { s3: 2, api: 1 }
// }
// Optimize when needed
if (stats.memoryUsage > threshold) {
await workspace.optimizeMemoryUsage();
}
```
### Efficient File Operations
```typescript
// Batch operations across projects
const operations = [
{ project: "dev", operation: () => dev.fs.writeFile("/a.txt", "content") },
{ project: "staging", operation: () => staging.fs.writeFile("/b.txt", "content") },
{ project: "prod", operation: () => prod.fs.readFile("/config.json") }
];
await Promise.all(operations.map(op => op.operation()));
// Use project capabilities efficiently
const provider = workspace.getProvider("s3");
if (provider?.getCapabilities().supportsGlob) {
const files = await s3Project.fs.glob("**/*.log");
} else {
// Fallback for providers without glob
const files = await this.manualGlob(s3Project, "**/*.log");
}
```
### Error Handling
```typescript
// Robust multi-project error handling
async function robustOperation() {
const results = { success: [], failed: [] };
for (const project of workspace.getProjects()) {
try {
await project.fs.writeFile("/health-check.txt", new Date().toISOString());
results.success.push(project.id);
} catch (error) {
console.warn(`Health check failed for ${project.id}:`, error.message);
results.failed.push({ project: project.id, error: error.message });
// Auto-disable problematic projects
if (error.message.includes("network") || error.message.includes("credentials")) {
await workspace.disableProject(project.id);
}
}
}
return results;
}
```
## ๐งช Testing
The workspace includes comprehensive test coverage with a focus on core functionality:
```bash
# Run all tests
pnpm test
# Core functionality tests
pnpm test tests/core/
# Specific test suites
pnpm test tests/core/ProjectManagement.test.ts # Load/unload/delete
pnpm test tests/core/ProjectStateManagement.test.ts # Enable/disable
pnpm test tests/core/CrossProjectOperations.test.ts # Multi-project ops
pnpm test tests/core/FileSystemProxy.test.ts # Transparent proxy
pnpm test tests/core/ProviderValidation.test.ts # Provider system
```
### Test Philosophy
- **Unit Tests**: Core workspace logic with mocked providers (fast, deterministic)
- **Integration Tests**: Real provider implementations (in individual packages)
- **Mocked Providers**: Complete FileSystem mocks with configurable capabilities
- **Edge Cases**: Orphaned references, capability limitations, error scenarios
## ๐๏ธ Architecture
The workspace system follows a **modular architecture** with clear separation of concerns:
### Core Modules
```
src/
โโโ WorkspaceFileSystem.ts # Main orchestrator class (~900 lines)
โโโ managers/ # Business logic managers
โ โโโ ProjectManager.ts # Project lifecycle and loading
โ โโโ PerformanceManager.ts # Memory optimization and metrics
โ โโโ PersistenceManager.ts # State persistence and restoration
โ โโโ EventManager.ts # Event forwarding and enrichment
โโโ operations/ # Cross-cutting operations
โ โโโ ProjectOperations.ts # Copy, sync, compare between projects
โ โโโ FileSystemProxy.ts # IReactiveFileSystem delegation
โโโ credentials/ # Credential management (provider-agnostic)
โโโ import-export/ # Workspace import/export functionality
โโโ interfaces/ # Core interfaces and types
```
### Design Principles
1. **Single Responsibility**: Each module handles one specific aspect
2. **Provider Agnostic**: Core knows nothing about specific implementations
3. **Dependency Injection**: Modules receive dependencies via constructor
4. **Event-Driven**: Reactive architecture with typed events
5. **Testable**: Small, focused modules that are easy to test
### Module Responsibilities
- **ProjectManager**: Handles project loading, unloading, and conversion
- **PerformanceManager**: Monitors memory usage and optimizes resources
- **PersistenceManager**: Manages database operations and state restoration
- **EventManager**: Forwards filesystem events with project context
- **ProjectOperations**: Implements cross-project operations
- **FileSystemProxy**: Delegates all filesystem calls to active project
## ๐ Documentation
Explore our comprehensive guides to master Firesystem Workspace:
- **[Project Lifecycle Management](./docs/01-project-lifecycle.md)** - Complete guide to loading, enabling/disabling, and deleting projects
- **[Event System Guide](./docs/02-event-system.md)** - Master the reactive event system for real-time updates
- **[Provider System Guide](./docs/03-provider-system.md)** - Learn how to work with providers and create custom storage implementations
- **[Cross-Project Operations](./docs/04-cross-project-operations.md)** - Unlock the power of multi-project workflows
- **[React Integration Guide](./docs/05-react-integration.md)** - Build modern React apps with Firesystem Workspace
For code examples, check out the [examples directory](./examples/).
## ๐ API Reference
### WorkspaceFileSystem
```typescript
class WorkspaceFileSystem implements IReactiveFileSystem {
// Project Management
async loadProject(config: ProjectConfig): Promise<Project>
async unloadProject(projectId: string): Promise<void>
getProject(projectId: string): Project | null
getProjects(): Project[]
async setActiveProject(projectId: string): Promise<void>
getActiveProject(): Project | null
// Project Lifecycle
async deleteProject(projectId: string, options?: DeleteProjectOptions): Promise<void>
async disableProject(projectId: string): Promise<void>
async enableProject(projectId: string): Promise<void>
async disableProjects(projectIds: string[]): Promise<void>
async enableProjects(projectIds: string[]): Promise<void>
isProjectEnabled(projectId: string): boolean
async getDisabledProjects(): Promise<StoredProject[]>
// Cross-Project Operations
async copyFiles(sourceId: string, pattern: string, targetId: string, targetPath: string): Promise<void>
async syncProjects(sourceId: string, targetId: string, options?: SyncOptions): Promise<void>
async compareProjects(projectId1: string, projectId2: string): Promise<ProjectDiff>
// Provider Management
registerProvider(provider: SourceProvider): void
unregisterProvider(scheme: string): void
getProvider(scheme: string): SourceProvider | undefined
getRegisteredProviders(): SourceProvider[]
// Performance & Statistics
async getProjectStats(): Promise<WorkspaceStats>
async getProjectMetrics(projectId: string): Promise<ProjectMetrics>
async optimizeMemoryUsage(): Promise<OptimizationReport>
// Discovery & Persistence
async discoverIndexedDBProjects(): Promise<ProjectConfig[]>
async exportWorkspace(options?: ExportOptions): Promise<WorkspaceExport>
async importFromUrl(url: string): Promise<void>
// All IReactiveFileSystem methods (proxied to active project)
async readFile(path: string): Promise<FileEntry>
async writeFile(path: string, content: any): Promise<FileEntry>
// ... (all file system operations)
// Event System
readonly events: TypedEventEmitter<WorkspaceEventPayloads>
// Lifecycle
async initialize(config?: WorkspaceConfig): Promise<void>
async clear(): Promise<void>
async close(): Promise<void>
}
```
### Key Interfaces
```typescript
interface Project {
id: string
name: string
source: ProjectSource
fs: IReactiveFileSystem
metadata: ProjectMetadata
state: "loading" | "loaded" | "error" | "disabled"
lastAccessed: Date
accessCount: number
memoryUsage: number
}
interface ProjectSource {
type: "memory" | "indexeddb" | "s3" | "github" | "api" | "json-url"
config: any
auth?: SourceAuth
}
interface DeleteProjectOptions {
deleteData?: boolean // Also delete project data (provider-specific)
skipConfirmation?: boolean // Skip confirmation event
}
interface SourceProvider {
readonly scheme: string
readonly displayName: string
createFileSystem(config: any): Promise<IReactiveFileSystem>
getCapabilities(): IFileSystemCapabilities
validateConfiguration?(config: any): Promise<{ valid: boolean; errors?: string[] }>
dispose?(fs: IReactiveFileSystem): Promise<void>
// Optional data management methods
deleteProjectData?(config: any): Promise<void>
hasLocalData?(): boolean
estimateDataSize?(config: any): Promise<number | null>
}
interface WorkspaceSettings {
maxActiveProjects?: number // Auto-disable when exceeded
autoDisableAfter?: number // Ms of inactivity before auto-disable
keepFocusedActive?: boolean // Never auto-disable focused project
autoSave?: boolean
autoSaveInterval?: number
memoryThreshold?: number // Bytes before optimization
}
```
## ๐ง Configuration
### Environment Variables
```bash
# S3 Configuration
FIRESYSTEM_S3_ACCESS_KEY_ID=your_access_key
FIRESYSTEM_S3_SECRET_ACCESS_KEY=your_secret_key
FIRESYSTEM_S3_REGION=us-east-1
# GitHub Configuration
FIRESYSTEM_GITHUB_TOKEN=ghp_xxxxxxxxxxxx
GITHUB_TOKEN=ghp_xxxxxxxxxxxx # Fallback
# API Configuration
FIRESYSTEM_API_API_KEY=your_api_key
FIRESYSTEM_API_BASE_URL=https://api.example.com
```
### Workspace Settings
```typescript
const workspace = new WorkspaceFileSystem({
settings: {
maxActiveProjects: 10, // Keep 10 projects active max
autoDisableAfter: 1800000, // 30 minutes
keepFocusedActive: true, // Protect focused project
autoSave: true, // Auto-save state changes
autoSaveInterval: 60000, // Save every minute
memoryThreshold: 512 * 1024 * 1024 // 512MB threshold
}
});
```
## ๐จ Migration Guide
### From Traditional Single-Project Tools
```typescript
// Before: Single project at a time
class OldWorkflow {
async switchToProject(projectId: string) {
await this.closeCurrentProject(); // Lost from memory
await this.openProject(projectId); // Load from scratch
}
async copyToOtherProject(file: string, targetProject: string) {
const content = await this.readFile(file);
await this.closeCurrentProject(); // Lose current context
await this.openProject(targetProject);
await this.writeFile(file, content);
await this.closeCurrentProject(); // Lose target context
await this.openOriginalProject(); // Reload original
}
}
// After: Multi-project simultaneous access
class NewWorkflow {
async switchToProject(projectId: string) {
await workspace.setActiveProject(projectId); // Just change focus
// All other projects remain active and accessible!
}
async copyToOtherProject(file: string, targetProjectId: string) {
const source = workspace.getActiveProject();
const target = workspace.getProject(targetProjectId);
const content = await source.fs.readFile(file);
await target.fs.writeFile(file, content);
// Both projects remain active, no context loss!
}
}
```
### Version History
| Version | Release Date | Key Features |
|---------|-------------|--------------|
| **v1.0.8** | 2025-07-03 | ๐๏ธ Modular architecture refactoring, improved separation of concerns |
| **v1.0.7** | 2025-07-02 | ๐๏ธ Complete deleteProject, provider data management |
| **v1.0.4** | 2025-06-30 | ๐งช Comprehensive test suite, robust validation |
| **v1.0.3** | 2025-06-30 | ๐งน Auto-cleanup, empty-by-design, robust validation |
| **v1.0.2** | 2025-06-30 | ๐ Complete workspace system with persistence |
| **v1.0.1** | 2025-06-30 | ๐ฆ Initial release with multi-project support |
## ๐ค Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
```bash
# Clone the repository
git clone https://github.com/firesystem/firesystem.git
cd firesystem/packages/workspace
# Install dependencies
pnpm install
# Run tests
pnpm test
# Type checking
pnpm typecheck
# Build
pnpm build
```
## ๐ Roadmap
### v1.1.0 - Enhanced Cloud Support
- [ ] Native S3FileSystem implementation
- [ ] Google Drive source provider
- [ ] Azure Blob Storage support
- [ ] Enhanced GitHub integration with commit/push
### v1.2.0 - Advanced Operations
- [ ] Project templates and scaffolding
- [ ] Advanced sync strategies
- [ ] Workspace-level search across all projects
- [ ] Project dependency management
### v1.3.0 - Performance & Scale
- [ ] Virtual file system for large projects
- [ ] Streaming file operations
- [ ] Background sync and preloading
- [ ] Advanced caching strategies
### v2.0.0 - Collaboration Features
- [ ] Real-time collaboration protocol
- [ ] Multi-user workspace sharing
- [ ] Role-based access control
- [ ] Audit logging and analytics
## ๐ License
MIT ยฉ Anderson D. Rosa
## ๐ Acknowledgments
- Built on top of the robust [@firesystem/core](../core) foundation
- Inspired by VSCode's multi-root workspace concept, but taken to the next level
- Thanks to the TypeScript team for excellent type system
- IndexedDB API for reliable browser persistence
- The open source community for continuous inspiration
---
**๐ฅ Transform your development workflow with true multi-project parallelism!**