Isolated npm package management for multi-tenant systems
npm install johanlabs-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.
* ๐ 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-lockfile prevents 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
``bash`
npm install johanlabs-librarian
`typescript
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']
});
`
Run untrusted code safely in isolated V8 contexts:
`typescript
// 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);
`
`typescript
// 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');
`
`typescript
// 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'
);
`
`typescript
// 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: 'admin@company.com'
}
});
// List all registries
const registries = tenant.listRegistry();
// Remove registry
tenant.removeRegistry('https://npm.company.com');
`
`typescript
// 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();
`
`typescript
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
`
``
๐ฆ 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
typescript
// โ Throws error
lib.tenant('../../etc/passwd');// โ
Safe
lib.tenant('user-123');
`$3
`typescript
// spawn() uses shell: false and validates inputs
// No way to inject shell commands
`$3
`typescript
// 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
`typescript
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(
);
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
$3
`typescript
class Librarian {
constructor(options?: LibrarianOptions)
static setLogLevel(level: LogLevel): void
tenant(id: string): Tenant
async disposeTenant(id: string): Promise
async disposeAll(): Promise
}
`$3
`typescript
class Tenant {
async addPackage(pkg: ManagedPackage, targetDir?: string): Promise
async removePackage(name: string): Promise
async runScript(code: string, options?: ScriptOptions): Promise
addRegistry(registry: RegistryConfig): Tenant
removeRegistry(url: string): Tenant
listRegistry(): RegistryConfig[]
listPackages(): ManagedPackage[]
get packages(): Record
async dispose(): Promise
}
`$3
`typescript
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
}
``ISC License - see LICENSE file for details.
For issues, questions, or contributions, please visit the GitHub repository.