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 🙏

© 2026 – Pkg Stats / Ryan Hefner

runtime-reporter

v0.8.0

Published

A framework-agnostic, type-safe reporting library that standardizes how clients handle errors and logs.

Readme

Runtime Reporter

A framework-agnostic, type-safe reporting library that standardizes how clients handle errors and logs.

// ./src/my-reporter.ts
import { createReporter } from "runtime-reporter";

export default createReporter({
    ERR01: "Something went wrong",
});

// ./src/MyComponent.ts
import myReporter from "./my-reporter";

export function MyComponent() {
    useEffect(() => {
        myReporter.error("ERR01");
    }, []);
}

Why?

Runtime Reporter solves these problems:

  • duplicated log messages
  • inconsistent error wording
  • fragile test assertions
  • accidental exposure of sensitive data

by introducing these features:

  • centralized and standardized error messaging
  • stable error codes for debugging and error tracking
  • test assertions without string duplication
  • safer production output (no sensitive data exposure)

Who is this for?

  • Front-end frameworks and libraries
  • Projects that need stable error codes
  • Teams replacing custom logging systems
  • Projects that want safer production output

Installation

npm install runtime-reporter

Quick start

A full, copy-and-paste example of how to use Runtime Reporter in your project:

import { createReporter, type RuntimeReporterMessages } from "runtime-reporter";

const messages: RuntimeReporterMessages<
    | {
          code: "ERR01";
          template: "{{ componentName }} failed to mount";
          tokens: "componentName";
      }
    | {
          code: "ERR02";
          template: "Failed to load configuration";
      }
    | {
          code: "ERR03";
          template: "Failed to fetch {{ resource }} from {{ url }}";
          tokens: "resource" | "url";
      }
> = {
    ERR01: "{{ componentName }} failed to mount",
    ERR02: "Failed to load configuration",
    ERR03: "Failed to fetch {{ resource }} from {{ url }}",
};

/** The runtime reporter for <project-name> */
const reporter = createReporter(
    process.env.NODE_ENV === "production" ? ({} as typeof messages) : messages
);

export default reporter;

Features

If you are new to Runtime Reporter, take a moment to explore its core features.

1. Code-based messaging

Replace inline strings with centralized, code-based identifiers.

// Without runtime-reporter
console.log("Something went wrong");
// ❌ Logs: "Something went wrong"

// With runtime-reporter
reporter.log("ERR01");
// ✅ Logs: "Something went wrong (ERR01)"

2. Dynamic messages

Inject runtime data into your messages via message templates and tokenized variables.

const reporter = createReporter({
    ERR01: "{{ componentName }} failed to mount",
});

reporter.error("ERR01", { componentName: "MyComponent" });
// ✅ Logs: "MyComponent failed to mount (ERR01)"

3. Production-ready

Pass an empty object to the createReporter function in production environments for better security and a smaller bundle size.

const reporter = createReporter(
    process.env.NODE_ENV === "production" ? ({} as typeof messages) : messages
);

Development environments get detailed messaging, while production environments get as little as possible.

reporter.error("ERR01");
// ✅ In development, it logs: "Something went wrong (ERR01)"
// ✅ In production, it does not log

reporter.fail("ERR01");
// ✅ In development, it throws: "Something went wrong (ERR01)"
// ✅ In production, it throws: "An error occurred (ERR01)"

4. Type safety

Annotate your messages to get autocomplete and compile-time validation for message codes and token names.

const messages: RuntimeReporterMessages<
    | {
          code: "ERR01";
          template: "{{ componentName }} failed to mount";
          tokens: "componentName";
      }
    | {
          code: "ERR02";
          template: "Failed to load configuration";
      }
> = {
    // ✅ Autocomplete
    ERR01: "{{ componentName }} failed to mount",
    ERR02: "Failed to load configuration",
};

const reporter = createReporter(messages);

// ✅ Autocomplete
reporter.error("ERR01", { componentName: "MyComponent" });

// ✅ No second argument needed when the message has no tokens
reporter.error("ERR02");

// ❌ TypeScript Error: "componentName" token is required
reporter.error("ERR01");

// ❌ TypeScript Error: "ERR03" is not a valid message code
reporter.error("ERR03", { componentName: "MyComponent" });

5. Test friendly

Assert against resolved messages without duplicating message text in your test environment.

it("should log error if component fails to mount", () => {
    vi.spyOn(console, "error").mockImplementation(() => {});

    render(<MyComponent />);

    expect(console.error).toHaveBeenCalledWith(
        reporter.message("ERR01", { componentName: "MyComponent" })
        // ✅ Asserts: "MyComponent failed to mount (ERR01)"
    );
});

6. Custom side effects

Leverage the onReport hook to perform custom actions (e.g., logging to remote services) when a report is made.

const reporter = createReporter(messages, {
    onReport: (payload) => {
        fetch("/api/reports", {
            method: "POST",
            body: JSON.stringify(payload),
        });
    },
});

reporter.error("ERR01");
// ✅ Sends a POST request with the following payload:
// {
//     code: "ERR01",
//     message: "MyComponent failed to mount",
//     level: "error",
// }

API

createReporter(RuntimeReporterMessages, options?: RuntimeReporterOptions): RuntimeReporter

Takes a messages object, an optional set of configuration options, and returns a reporter object.

RuntimeReporter

| Method | Type | Description | | ------- | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | message | (code: string, tokens?: RuntimeReporterTokens) => string | Returns the resolved message (no logging); useful for testing environments. The tokens argument is only accepted when that code declares tokens. | | warn | (code: string, tokens?: RuntimeReporterTokens) => void | Logs via console.warn only if the template exists in messages. The tokens argument is required for tokenized messages and omitted otherwise. | | error | (code: string, tokens?: RuntimeReporterTokens) => void | Logs via console.error only if the template exists in messages. The tokens argument is required for tokenized messages and omitted otherwise. | | log | (code: string, tokens?: RuntimeReporterTokens) => void | Logs via console.log only if the template exists in messages. The tokens argument is required for tokenized messages and omitted otherwise. | | fail | (code: string, tokens?: RuntimeReporterTokens) => never | Throws new Error(resolvedMessage) in all environments. Uses the defaultTemplate when the template does not exist in messages. The tokens argument is required for tokenized messages and omitted otherwise. |

RuntimeReporterOptions

| Property | Type | Required | Description | | --------------- | ------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | formatMessage | (message: string, code: string) => string | No | Customize the final output of every message. By default, messages are in the format: "<message> (<code>)". This option does not affect the message provided to the onReport hook. | | defaultTemplate | string | No | Fallback message text used when the code does not exist in messages. Defaults to "An error occurred". This is mostly relevant for fail() in production when you pass an empty message set. | | onReport | (payload: RuntimeReporterReportPayload) => void | No | A hook to perform custom actions when a report is made via error, warn, log, or fail. |

RuntimeReporterTokens

| Property | Type | Description | | -------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | string | RuntimeReporterToken | A record of token names along with their replacement value. Supported types include: string, number, boolean, Error. |

RuntimeReporterReportPayload

| Property | Type | Description | | --------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | code | string | The unique code associated with the message. | | message | string | The resolved message text; the placeholders have been replaced by their token values but, it has not been formatted by the formatMessage option. | | level | "error" \| "warn" \| "log" \| "fail" | The severity level of the report. |

Examples

Custom formatting

You can customize the format of the message by providing a custom formatMessage function.

const reporter = createReporter(messages, {
    formatMessage: (message, code) => `[${code}] ${message}`,
});

reporter.message("ERR01", { componentName: "MyComponent" });
// "[ERR01] MyComponent failed to mount"

Calling fail() in production

When the createReporter function provided an empty message set in production, the fail method will use the customizable defaultTemplate option which defaults to "An error occurred". This message is intended to be generic so that it does not reveal sensitive information about the system, while still providing a code for debugging purposes.

const reporter = createReporter(
    process.env.NODE_ENV === "production" ? ({} as typeof messages) : messages
);

reporter.fail("ERR01");
// throws: "An error occurred (ERR01)"

Using message() in tests

The message method returns the resolved string without side effects, allowing you to validate precise messaging without duplicating text.

it("should log error if component fails to mount", () => {
    vi.spyOn(console, "error").mockImplementation(() => {});

    render(<MyComponent />);

    expect(console.error).toHaveBeenCalledWith(
        reporter.message("ERR01", { componentName: "MyComponent" })
    );
});

Messages without tokens

If a message type does not declare tokens, do not pass a second argument.

const messages: RuntimeReporterMessages<
    | {
          code: "ERR01";
          template: "{{ componentName }} failed to mount";
          tokens: "componentName";
      }
    | {
          code: "INFO01";
          template: "Ready";
      }
> = {
    ERR01: "{{ componentName }} failed to mount",
    INFO01: "Ready",
};

const reporter = createReporter(messages);

reporter.message("INFO01");
reporter.log("INFO01");

Using onReport and formatMessage together

By combining the onReport and formatMessage options, you can have granular control over the message output and reporting behavior. For example, you could log a generic message to users while still sending the full payload to a remote service in production environments. At the same time, you can log the full message in non-production environments for debugging purposes.

const reporter = createReporter(messages, {
    formatMessage: (message, code) => {
        if (process.env.NODE_ENV === "production") {
            return "Generic error message ...";
        } else {
            return `${message} (${code})`;
        }
    },
    onReport: (payload) => {
        if (process.env.NODE_ENV === "production") {
            fetch("/api/reports", {
                method: "POST",
                body: JSON.stringify(payload),
            });
        }
    },
});

reporter.error("ERR01");
// ✅ In production, users get a generic message AND remote service gets the full message
// ✅ In non-production, developers get the full message

Type safety without TypeScript

You can still get the same benefits as TypeScript by using JSDoc-style type annotations.

/**
 * @type {import("runtime-reporter").RuntimeReporterMessages<{
 *     code: "ERR01";
 *     template: "{{ componentName }} failed to mount";
 *     tokens: "componentName";
 * } | {
 *     code: "ERR02";
 *     template: "Failed to load configuration";
 * } | {
 *     code: "ERR03";
 *     template: "Failed to fetch {{ resource }} from {{ url }}";
 *     tokens: "resource" | "url";
 * }>}
 */
const messages = {
    ERR01: "{{ componentName }} failed to mount",
    ERR02: "Failed to load configuration",
    ERR03: "Failed to fetch {{ resource }} from {{ url }}",
};

CommonJS support

If needed, Common JS imports are also available.

const { createReporter } = require("runtime-reporter");