npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

emitnlog

v0.13.0

Published

Emit n' Log: a modern, type-safe library for logging, event notifications, and observability in JavaScript/TypeScript apps.

Readme

NPM GitHub release (latest SemVer) CI Coverage License

Emit n' Log

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
  • ESM and CJS supported

Quick Start

npm install emitnlog

Logger

Clean, template-literal logging with multiple output formats and lazy evaluation:

import { createConsoleLogLogger } from 'emitnlog/logger';

const logger = createConsoleLogLogger();

// 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}`;

→ Full Logger Documentation

Event Notifier

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();

→ Full Notifier Documentation

Function & Promise Tracking

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.stage.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
]);

→ Full Tracker Documentation

Utilities

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

Features

  • 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-Aware: Automatically detects NodeJS vs browser/neutral environments
  • Promise-Friendly: First-class async/await support

Documentation

| 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 |

Logger + Notifier Example

Here's how the logger and notifier components work together:

import { createEventNotifier } from 'emitnlog/notifier';
import { createConsoleLogLogger } from 'emitnlog/logger';

type Progress = { filename: string; percent: number };

class FileUploader {
  private _logger = createConsoleLogLogger('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');

Advanced Import Concepts

This section is for developers who want more control over how modules are resolved and used, especially in projects that need to support multiple runtime environments. If you're just getting started, the standard imports shown on the examples will "just work" — no special handling required.

Runtime-Specific Imports

By default, emitnlog automatically detects the runtime environment (NodeJS or neutral like browser/edge runtimes) and loads the appropriate implementation. However, if you're building a library or SDK and want stricter control — for example, to guarantee that only runtime-neutral features are included, you can explicitly import from the emitnlog/neutral variant:

import { createConsoleLogLogger } from 'emitnlog/neutral/logger';

This ensures your code remains portable and avoids accidentally depending on Node-only features like file-based loggers.

Import Styles

You can import from emitnlog using different styles depending on your project structure and preferences.

The examples use path imports for clarity, but you have three import styles to choose from:

Path Imports

import { createConsoleLogLogger } from 'emitnlog/logger';
import { createEventNotifier } from 'emitnlog/notifier';
import { createInvocationTracker } from 'emitnlog/neutral/tracker';
import { debounce } from 'emitnlog/utils';

Flat Imports

import { createConsoleLogLogger, createEventNotifier, createInvocationTracker, debounce } from 'emitnlog';

Namespace Imports

import { logging, notifying, tracking, utils } from 'emitnlog/neutral';

const logger = logging.createConsoleLogLogger();
const notifier = notifying.createEventNotifier();
const tracker = tracking.createInvocationTracker();
const debouncedFn = utils.debounce(fn, 500);

Path imports are used throughout the examples because they:

  • Make it clear which module each function comes from
  • Enable better tree-shaking in bundlers
  • Provide more predictable IDE auto-imports

But feel free to use whichever style fits your project best!

All styles and import paths are compatible with both CommonJS and ESM environments.

API Reference

See source JSDoc for complete API documentation with examples.

License

MIT