A modern, type-safe library for logging, event notifications, and observability in JavaScript/TypeScript apps.
Perfect for modern projects that need:
- Clear, structured logging with lazy evaluation
- Lightweight event notifications without heavy observable libraries
- Function call tracking with detailed lifecycle events
- Promise coordination, deduplication, and caching
- Zero dependencies with full TypeScript support
npm install emitnlog
Clean, template-literal logging with multiple output formats and lazy evaluation:
import { ConsoleLogger } from 'emitnlog/logger';
const logger = new ConsoleLogger();
// Template logging with lazy evaluation
const userId = 'user123';
logger.i`User ${userId} logged in successfully`;
// Only computed when log level matches
logger.d`Expensive calculation: ${() => performExpensiveOperation()}`;
// Rich object logging
const data = { id: 123, items: ['a', 'b', 'c'] };
logger.d`Request data: ${data}`;
Simple, type-safe event notifications with lazy evaluation:
import { createEventNotifier } from 'emitnlog/notifier';
const notifier = createEventNotifier<string>();
// Subscribe to events
const subscription = notifier.onEvent((msg) => {
console.log(`Received: ${msg}`);
});
// Notify with lazy evaluation
notifier.notify(() => {
return expensiveEventData(); // Only called if listeners exist
});
// Promise-based event waiting
const nextEvent = await notifier.waitForEvent();
Monitor function calls, coordinate async operations, and cache expensive computations:
import { createInvocationTracker, trackPromises, holdPromises } from 'emitnlog/tracker';
// Function call tracking
const tracker = createInvocationTracker();
tracker.onCompleted((inv) => console.log(`${inv.key.operation} took ${inv.duration}ms`));
const login = tracker.track('login', async (user) => {
await authenticateUser(user);
});
// Promise coordination
const promiseTracker = trackPromises();
promiseTracker.track('cleanup', database.close());
await promiseTracker.wait(); // Wait for all tracked promises
// Promise deduplication
const holder = holdPromises();
const [user1, user2] = await Promise.all([
holder.track('user-123', () => fetchUser(123)),
holder.track('user-123', () => fetchUser(123)), // Same promise, no duplicate fetch
]);
Helpful utilities for async operations, type safety, and data handling:
import { debounce, withTimeout, stringify, exhaustiveCheck } from 'emitnlog/utils';
// Debounced operations
const debouncedSave = debounce(saveData, 500);
await debouncedSave(data);
// Timeout handling
const result = await withTimeout(longRunningOperation(), 5000, 'timeout');
// Safe stringification
const logMessage = stringify(complexObject);
// Exhaustive type checking
function handleStatus(status: 'pending' | 'success' | 'error') {
switch (status) {
case 'pending':
return 'Loading...';
case 'success':
return 'Done!';
case 'error':
return 'Failed!';
default:
return exhaustiveCheck(status); // Compile-time error if case missed
}
}
→ Full Utilities Documentation
- 🎯 Type-Safe: Built with TypeScript from the ground up
- 🪶 Lightweight: Zero dependencies, minimal runtime overhead
- ⚡ Lazy Evaluation: Compute values only when needed
- 🔧 Flexible: Multiple logger targets, customizable formats
- 🎪 Environment-Driven: Configure via environment variables
- 🔄 Promise-Friendly: First-class async/await support
Component | Description | Documentation |
---|---|---|
Logger | Structured logging with template literals and multiple outputs | docs/logger.md |
Notifier | Type-safe event notifications with lazy evaluation | docs/notifier.md |
Tracker | Function call tracking, promise coordination, and caching | docs/tracker.md |
Utilities | Async helpers, type guards, and data utilities | docs/utilities.md |
Here's how the logger and notifier components work together:
import { createEventNotifier } from 'emitnlog/notifier';
import { ConsoleLogger } from 'emitnlog/logger';
type Progress = { filename: string; percent: number };
class FileUploader {
private _logger = new ConsoleLogger('debug');
private _notifier = createEventNotifier<Progress>();
public onProgress = this._notifier.onEvent;
public upload(filename: string) {
this._logger.i`Starting upload of ${filename}`;
for (let i = 0; i <= 100; i += 25) {
this._notifier.notify(() => ({ filename, percent: i }));
this._logger.d`Progress for ${filename}: ${i}%`;
}
this._logger.i`Finished upload of ${filename}`;
}
}
const uploader = new FileUploader();
const subscription = uploader.onProgress(({ filename, percent }) => {
renderProgress(filename, percent);
});
uploader.upload('video.mp4');
See source JSDoc for complete API documentation with examples.