johanlabs-librarian
v0.2.2
Published
Isolated npm package management for multi-tenant systems
Maintainers
Readme
Librarian 📚
Librarian is a production-ready, multi-tenant workspace manager for Node.js. It provides isolated npm environments for different users (tenants) with enterprise-grade security, atomic operations, and V8 Isolates for safe code execution.
🚀 Key Features
- 🔒 Enterprise Security: Path traversal protection, input sanitization, and secure command execution
- ⚡ True Isolation: V8 Isolates with configurable memory limits and CPU time restrictions
- 🔐 Atomic Operations: Battle-tested file locking with
proper-lockfileprevents race conditions - 📦 Smart Cleanup: Dependency tree analysis ensures only orphaned packages are removed
- 🔄 Transaction Safety: Automatic backup/rollback on failures maintains data consistency
- 📊 Structured Logging: Four log levels (DEBUG, INFO, WARN, ERROR) for observability
- ⚙️ Fully Configurable: Timeouts, memory limits, and paths can be customized
- 🎯 Multi-Registry: Support for NPM, Verdaccio, and custom private registries
- ♻️ Resource Management: Automatic cleanup with
dispose()prevents memory leaks - 🧩 Context Pooling: Reusable V8 contexts for optimal performance
📦 Installation
npm install johanlabs-librarian🎯 Quick Start
import { Librarian, LogLevel } from 'johanlabs-librarian';
// Configure logging (optional)
Librarian.setLogLevel(LogLevel.INFO);
// Initialize with custom options
const lib = new Librarian({
path: './workspaces',
configName: 'packages.json', // Custom manifest filename
lockTimeout: 180000, // 3 minutes
installTimeout: 600000, // 10 minutes
memoryLimit: 256 // 256MB for isolates
});
// Create a tenant workspace
const tenant = lib.tenant('user-123');
// Add packages
await tenant.addPackage({ name: 'lodash', version: '4.17.21' });
await tenant.addPackage({ name: 'axios', version: '^1.6.0' });
// Configure private registry
tenant.addRegistry({
type: 'custom',
url: 'https://registry.company.com',
auth: { token: 'npm_xxxxx' },
scopes: ['@company']
});🔐 Secure Script Execution
Run untrusted code safely in isolated V8 contexts:
// Scripts cannot access host process, require, or file system
const result = await tenant.runScript(`
const packagesJSON = packages; // Injected as JSON string
const data = JSON.parse(packagesJSON);
// Your isolated code here
return JSON.stringify({ status: 'ok', data });
`, {
timeout: 5000, // 5 seconds max
cpuTimeLimit: 100 // 100ms CPU time
});
console.log(result);📚 Advanced Usage
Direct Package Access (Trusted Environment)
// Access installed packages directly in your main process
const { lodash, axios } = tenant.packages;
const chunked = lodash.chunk(['a', 'b', 'c', 'd'], 2);
const response = await axios.get('https://api.example.com');Package Management
// List installed packages
const packages = tenant.listPackages();
console.log(packages); // [{ name: 'lodash', version: '4.17.21' }, ...]
// Remove package (auto-cleanup)
await tenant.removePackage('lodash');
// Custom installation directory
await tenant.addPackage(
{ name: 'module-alias', version: '2.2.3' },
'custom_modules'
);Registry Management
// NPM Registry
tenant.addRegistry({
type: 'npm',
url: 'https://registry.npmjs.org'
});
// Private Registry with Token
tenant.addRegistry({
type: 'custom',
url: 'https://npm.company.com',
auth: { token: 'npm_xxxxxxxxxxxx' },
scopes: ['@company', '@internal']
});
// Private Registry with Basic Auth
tenant.addRegistry({
type: 'verdaccio',
url: 'http://localhost:4873',
auth: {
username: 'admin',
password: 'secret',
email: '[email protected]'
}
});
// List all registries
const registries = tenant.listRegistry();
// Remove registry
tenant.removeRegistry('https://npm.company.com');Resource Management
// Dispose single tenant (releases locks, isolates, cache)
await lib.disposeTenant('user-123');
// Or dispose from tenant instance
await tenant.dispose();
// Dispose all tenants (graceful shutdown)
await lib.disposeAll();Logging Configuration
import { LogLevel } from 'johanlabs-librarian';
// Set log level globally
Librarian.setLogLevel(LogLevel.DEBUG); // Most verbose
Librarian.setLogLevel(LogLevel.INFO); // Default
Librarian.setLogLevel(LogLevel.WARN); // Warnings only
Librarian.setLogLevel(LogLevel.ERROR); // Errors only🏗️ Architecture
📦 Librarian
├── 🏢 Librarian.ts - Main factory, tenant orchestration
├── 👤 Tenant.ts - Tenant workspace manager
├── 📦 package/
│ ├── PackageStore.ts - Manifest + npm install (async, transactional)
│ └── PackageCleaner.ts - Dependency tree cleanup
├── 🔐 lock/
│ └── TenantLock.ts - Atomic file locking (proper-lockfile)
├── 🌐 registry/
│ ├── RegistryManager.ts - Registry validation & management
│ └── NpmRcWriter.ts - .npmrc generation
├── 🎭 isolate/
│ └── IsolateManager.ts - V8 isolates + context pooling
└── 🛠️ utils/
├── Logger.ts - Structured logging
└── PathValidator.ts - Security validations🔒 Security Features
Path Traversal Protection
// ❌ Throws error
lib.tenant('../../etc/passwd');
// ✅ Safe
lib.tenant('user-123');Command Injection Prevention
// spawn() uses shell: false and validates inputs
// No way to inject shell commandsInput Validation
// Invalid URLs rejected
tenant.addRegistry({ url: 'javascript:alert(1)' }); // ❌ Error
// Invalid scopes rejected
tenant.addRegistry({ scopes: ['no-at-sign'] }); // ❌ Error⚡ Performance Features
- Async I/O: All file operations use
fs/promises - Context Pooling: Reuses up to 5 V8 contexts
- Smart Caching: Package cache with invalidation
- Dependency Tree: Only scans when needed
- Atomic Locks: Minimal lock duration
🧪 Quick Test
import { Librarian, LogLevel } from 'johanlabs-librarian';
Librarian.setLogLevel(LogLevel.DEBUG);
const lib = new Librarian({ path: './test-workspaces' });
const tenant = lib.tenant('test-user');
try {
// Test package installation
console.log('Installing lodash...');
await tenant.addPackage({ name: 'lodash', version: '4.17.21' });
// Test direct access
const { lodash } = tenant.packages;
console.log('Lodash chunk:', lodash.chunk([1, 2, 3, 4], 2));
// Test isolated execution
const result = await tenant.runScript(`
const pkgs = JSON.parse(packages);
'Packages: ' + Object.keys(pkgs).join(', ');
`);
console.log('Script result:', result);
// Cleanup
await tenant.dispose();
console.log('✅ All tests passed!');
} catch (err) {
console.error('❌ Test failed:', err);
}📊 Production Checklist
- ✅ Configure appropriate timeouts
- ✅ Set production log level (INFO or WARN)
- ✅ Implement graceful shutdown with
disposeAll() - ✅ Monitor lock timeouts in logs
- ✅ Set memory limits based on workload
- ✅ Use error handling around all async operations
- ✅ Validate tenant IDs from user input
- ✅ Consider rate limiting per tenant
🤝 API Reference
Librarian Class
class Librarian {
constructor(options?: LibrarianOptions)
static setLogLevel(level: LogLevel): void
tenant(id: string): Tenant
async disposeTenant(id: string): Promise<void>
async disposeAll(): Promise<void>
}Tenant Class
class Tenant {
async addPackage(pkg: ManagedPackage, targetDir?: string): Promise<Tenant>
async removePackage(name: string): Promise<Tenant>
async runScript(code: string, options?: ScriptOptions): Promise<any>
addRegistry(registry: RegistryConfig): Tenant
removeRegistry(url: string): Tenant
listRegistry(): RegistryConfig[]
listPackages(): ManagedPackage[]
get packages(): Record<string, any>
async dispose(): Promise<void>
}Types
interface LibrarianOptions {
path?: string; // Default: process.cwd()
configName?: string; // Default: 'list.json'
lockTimeout?: number; // Default: 120000 (2 min)
installTimeout?: number; // Default: 300000 (5 min)
memoryLimit?: number; // Default: 128 (MB)
}
interface ManagedPackage {
name: string;
version: string;
targetDir?: string;
}
interface RegistryConfig {
type: 'npm' | 'verdaccio' | 'custom';
url: string;
auth?: RegistryAuth;
scopes?: string[];
}
interface RegistryAuth {
token?: string;
username?: string;
password?: string;
email?: string;
}
enum LogLevel {
DEBUG = 0,
INFO = 1,
WARN = 2,
ERROR = 3
}📄 License
ISC License - see LICENSE file for details.
🙋 Support
For issues, questions, or contributions, please visit the GitHub repository.
🔄 Changelog
v0.2.0 (Production-Ready)
- ✅ Security: Path traversal protection, input validation
- ✅ Stability: proper-lockfile integration, transaction rollback
- ✅ Performance: Async I/O, context pooling, smart caching
- ✅ Observability: Structured logging with 4 levels
- ✅ Reliability: Dependency tree cleanup, timeout controls
- ✅ API: Resource management with dispose()
v0.1.0 (Initial Release)
- Basic multi-tenant package management
- V8 Isolates integration
- Registry support
