UNPKG

@workspace-fs/core

Version:

Multi-project workspace manager for Firesystem with support for multiple sources

1,291 lines (1,023 loc) โ€ข 40 kB
# @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. [![npm version](https://badge.fury.io/js/@firesystem%2Fworkspace.svg)](https://badge.fury.io/js/@firesystem%2Fworkspace) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0-blue.svg)](https://www.typescriptlang.org/) [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://opensource.org/licenses/MIT) [![Tests](https://img.shields.io/badge/tests-373%20passed-brightgreen.svg)](/) ## ๐Ÿš€ 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!**