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

eventflowjs

v0.1.12

Published

Lightweight event lifecycle logging for Node and browser apps

Downloads

1,170

Readme

EventFlow

NPM Downloads Weekly NPM Version Dependencies

EventFlow is a lightweight TypeScript library for event lifecycle logging.

Instead of scattering many log statements, you create one event, enrich it as work progresses, and emit a final structured log when complete.

Why EventFlow

  • Lifecycle-oriented logging (start -> enrich -> step -> end)
  • Structured JSON output for easy ingestion and searching
  • Works in Node.js, browsers, and fullstack flows
  • Async-safe event isolation on Node via AsyncLocalStorage
  • 0 dependencies

Installation

npm install eventflowjs

Examples Quickstart

Run a native / web version to try it out here:

Quick Start

import { EventFlow } from "eventflowjs";

EventFlow.startEvent("createUser");
EventFlow.addContext({ userId: 123, email: "[email protected]" });
EventFlow.step("create-db-record");
EventFlow.step("send-email");
EventFlow.endEvent();

Custom Transports

import { ConsoleTransport, EventFlow, Transport } from "eventflowjs";

class HttpTransport extends Transport {
    log(event: Transport.EventLog): void {
        void fetch("/logs", {
            method: "POST",
            headers: { "content-type": "application/json" },
            body: JSON.stringify(event),
        });
    }
}

EventFlow.configure({
    transports: [
    new ConsoleTransport({
        emissionMode: "errors-only",
        nonErrorSampleRate: 100,
        debug: true,
    }),
    new HttpTransport({ nonErrorSampleRate: 25 }),
    ],
});

emissionMode defaults to "all" and can be set to "errors-only". nonErrorSampleRate defaults to 100 and controls what percentage of non-failed events are emitted. debug defaults to false. When true, suppressed successful events trigger a simple Successful Event debug message.

Example Fullstack Flow w/ Stripe

Send event to API

// client/src/components/CartCheckout.tsx
EventFlow.startEvent("user_checkout");
EventFlow.step("client.user_press_checkout");
EventFlow.addContext({ cartID });
EventFlow.addUserContext(user); // see configuration
EventFlow.step("client.create_payment_intent");
const { paymentIntentID, clientSecret, continuationToken } = await fetch(
    "/api/createPaymentIntent",
    {
        method: "POST",
        body,
        headers: EventFlow.getPropagationHeaders(),
    },
);

Receive event from Client, prepare metadata for Webhook, send event back to Client

// api/index.ts
import { eventFlowMiddleware } from "eventflowjs";
app.use(eventFlowMiddleware);

// api/routes/createPaymentIntent.ts
app.post("/createPaymentIntent", async (req, res) => {
    const body = req.body;
    EventFlow.step("api.received_create_pi")
    EventFlow.addContext({ body });

    const metadata = EventFlow.getPropagationMetadata();
    const paymentIntent = await stripe.paymentIntents.create({
        ...
        metadata,
        ...
    });

    const clientSecret = process.env.STRIPE_CLIENT_SECRET;
    const paymentIntentID = paymentIntent.id;

    EventFlow.step("api.sending_back_to_client");
    EventFlow.addContext({ paymentIntentID });
    EventFlow.addEncryptedContext({ clientSecret })
    const continuationToken = EventFlow.getContinuationToken();
    res.json({ paymentIntentID, clientSecret, continuationToken });
});

Receive Event from API

// client/src/components/CartCheckout.tsx
EventFlow.continueFromToken(continuationToken);
EventFlow.addContext({ paymentIntentID });
EventFlow.step("client.present_payment_sheet");
const { error } = await presentPaymentSheet(paymentIntentID, clientSecret);
if (error) return handleError(error);

EventFlow.step("client.payment_success");
EventFlow.endEvent();

Receive event from Webhook

// api/routes/webhook
if (event.type === "payment_intent.succeeded") {
    const paymentIntent = event.data.object;

    EventFlow.fromMetadata(paymentIntent.metadata);
    EventFlow.step("webhook.payment_successful");
    EventFlow.addContext({ receiptNumber });
    EventFlow.endEvent();

    res.json({ received: true });
}

Final Output:

Note 3 emissions

  • client side
  • API route
  • API webhook
{
  "id": "evt_abc123",
  "name": "user_checkout",
  "status": "success",
  "timestamp": "2026-03-02T12:00:00.000Z",
  "duration_ms": 4678, // for webhook, client/api will be shorter
  "context": {
    "cartID": "abc123",
    "user": {
        "userID": "abc123",
        "email": "[email protected]",
    },
    "body": {
        "amount": 2500,
        "currency": "AUD"
    },
    "paymentIntentID": "pi_12345",
    "receiptNumber": "r_12345", // webhook only
  },
  "encryptedContext": {
    "clientSecret": "pi_secret_12345" // api/client only
  },
  "steps": [
    { "name": "client.user_press_checkout", "t": 30 },
    { "name": "client.create_payment_intent", "t": 60 }
    { "name": "api.received_create_pi", "t": 686 }
    { "name": "api.sending_back_to_client", "t": 959 } // api/client only
    { "name": "client.present_payment_sheet", "t": 1678 } // client only
    { "name": "client.payment_success", "t": 3652 } // client only
    { "name": "webhook.payment_successful", "t": 4678 } // webhook only
  ],
  "caller": { "file": "CartCheckout.tsx", "line": 42, "function": "onPressCheckout" },
  "traceId": "trc_xyz"
}

// if an error occurred
{
    ...
    "status": "failed",
    "context": {
        ...
        "customErrorContext": ...
    },
    "error": {
        "message": "payment declined",
        "stack": ...
    }
    ...
}

Client Configuration

Use configure to control client-level behavior:

import { EventFlow, type EventFlowClient } from "eventflowjs";

// user / account object on your platform
interface User { uid: string; email: string, ... };

// for typing `getUserContext`
const AppEventFlow: EventFlowClient<User> = EventFlow;

AppEventFlow.configure({
  showFullErrorStack: false,
  branding: false,
  encryptionKey: "shared-eventflow-key",
  getUserContext: (user) => ({
    email: user.email,
    id: user.uid,
  }),
});

export { AppEventFlow as EventFlow };

// then later:
EventFlow.startEvent("checkout");
EventFlow.addUserContext(user); // type safe
EventFlow.endEvent();

showFullErrorStack defaults to true. When set to false, emitted failed events include only the first two lines of error.stack. branding defaults to true. When set to false, ConsoleTransport logs raw JSON without the [EventFlow] prefix. transports optionally replaces active transport(s) in the same configure call (equivalent to calling setTransport(...)). encryptionKey is optional. When set, encryptedContext is encrypted in propagation headers, continuation tokens, and propagation metadata, then decrypted again in fromHeaders, continueFromToken, and fromMetadata. getUserContext configures addUserContext(account) to map your app-level user/account object into context.user. When context.user already exists, addUserContext overwrites it and logs a warning.

TypeScript note: assertion-based narrowing requires an explicitly typed local reference (for example, const AppEventFlow: EventFlowClient<User> = EventFlow;). Configure and use that reference in the same scope for typed addUserContext(...) calls.

Encrypted Context

Use addEncryptedContext when fields should stay readable in emitted logs but be encrypted while crossing service boundaries.

EventFlow.configure({ encryptionKey: "shared-eventflow-key" });

EventFlow.startEvent("checkout");
EventFlow.addContext({ cartId: "cart_1001" });
EventFlow.addEncryptedContext({
  customerEmail: "[email protected]",
  paymentIntentSecret: "pi_secret_123",
});

const headers = EventFlow.getPropagationHeaders();
// `headers` now contain encrypted `encryptedContext` values.

EventFlow.fromHeaders(headers);
console.log(EventFlow.getCurrentEvent()?.encryptedContext.paymentIntentSecret);
// "pi_secret_123"

context and encryptedContext are separate event fields. EventFlow encrypts only the values inside encryptedContext; key names remain visible so downstream services know which fields belong in the encrypted bucket.

Run Helper

EventFlow.run wraps a function call in a lifecycle-aware step. It catches errors, records failure in the event, and rethrows the error for normal propagation.

await EventFlow.run("payment", async (event) => {
    await payment();
});

Supported call signatures:

await EventFlow.run("step-name", async (event) => { ... }, options);
await EventFlow.run(async (event) => { ... }, options);

Useful options:

  • failEventOnError (default true): call EventFlow.fail(error) before rethrowing.
  • startIfMissing (default false): auto-start an event if none exists.
  • eventName: event name used when startIfMissing starts a new event.
  • endIfStarted (default true): auto-end only the event started by this run.
  • statusOnAutoEnd (default "success"): status used when auto-ending.

Instrument Helper

EventFlow.instrument creates a reusable wrapped function with the same error behavior as run.

const createUser = EventFlow.instrument("createUser", async (data) => {
    return db.createUser(data);
});

const user = await createUser({ email: "[email protected]" });

Defaults are wrapper-friendly:

  • auto-start event when missing
  • auto-end if the wrapper started it
  • fail + rethrow on error

Optional instrument options:

  • all run options (failEventOnError, startIfMissing, eventName, endIfStarted, statusOnAutoEnd)
  • stepName: override the step recorded for each call
  • contextFromArgs(...args): add context derived from input args
  • contextFromResult(result, ...args): add context from the returned value

React Native

If you're having issues in React-Native you can import from eventflowjs/react-native, and raise an issue to get it sorted.

  • import { EventFlow } from "eventflowjs/react-native";

API Reference

Primary Exports

| Export | Description | | -------------------------- | ------------------------------------------------------------------------------------------ | | EventFlow | Singleton EventFlowClient instance used for lifecycle logging. | | EventFlowClient | Class implementation behind the EventFlow singleton. | | eventflowjs/react-native | React Native entrypoint that exports EventFlow wired to browser-style in-memory context. | | eventFlowMiddleware | Ready-to-use Node/Express middleware (app.use(eventFlowMiddleware)). | | ConsoleTransport | Built-in JSON console transport. | | Transport | Base class for custom transports. Extend it and implement log(event). |

Utility Functions

| Function | Description | Arguments | Returns | | --------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------ | | createEventFlowMiddleware(client, options?) | Factory for custom middleware behavior. | client: compatible EventFlow client, options?: EventFlowMiddlewareOptions | EventFlowMiddleware | | serializeEvent(event, options?) | Serializes an event payload for transport. | event: EventLog, options?: { encryptionKey?: string } | string | | deserializeEvent(data, options?) | Parses serialized propagation payload safely. | data: string, options?: { encryptionKey?: string } | SerializedPropagationEvent or null | | getPropagationHeaders(event, options?) | Builds header propagation map from event. | event: EventLog, options?: { encryptionKey?: string } | Record<string, string> | | extractEventFromHeaders(headers, options?) | Rehydrates propagation payload from headers. | headers: HeadersLike, options?: { encryptionKey?: string } | SerializedPropagationEvent or null | | getPropagationMetadata(event, options?) | Builds metadata propagation map from event. | event: EventLog, options?: PropagationMetadataOptions | PropagationMetadata | | extractEventFromMetadata(metadata, options?)| Rehydrates propagation payload from metadata map. | metadata: PropagationMetadataInput, options?: { encryptionKey?: string } | SerializedPropagationEvent or null |

EventFlow Methods

| Method | Description | Arguments | Returns | | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------- | | startEvent(name) | Starts a new event. Auto-cancels and emits any currently active event first. | name: string | EventLog | | addContext(data) | Shallow-merges context into the active event. No-op if no active event exists. | data: EventContext | void | | addEncryptedContext(data) | Shallow-merges data into encryptedContext. Throws if encryptionKey is not configured. No-op if no event is active. | data: EventContext | void | | addUserContext(account) | Maps a configured user/account object and writes it to context.user. Throws if getUserContext is not configured. No-op if no event is active. | account: TAccount | void | | step(name) | Appends a step with elapsed time from event start. | name: string | void | | endEvent(status?) | Completes and emits the active event. | status?: EventStatus (default "success") | EventLog or null | | fail(error) | Marks active event as failed, captures error, emits, clears current event. | error: unknown | EventLog or null | | configure(options) | Updates client-level behavior settings. | options: EventFlowClientConfigureOptions | void | | getCurrentEvent() | Returns current active event in context. | none | EventLog or null | | setTransport(transport) | Replaces transport(s) used for emitting events. | transport: Transport or Transport[] | void | | getPropagationHeaders() | Builds propagation headers from active event. | none | Record<string, string> | | fromHeaders(headers) | Rehydrates and attaches event from propagation headers. | headers: HeadersLike | EventLog or null | | attach(event) | Attaches a provided event payload as current active event. | event: EventLog or SerializedPropagationEvent | EventLog | | getContinuationToken(event?) | Serializes an event for server->client or worker continuation. | event?: EventLog (defaults to current event) | string or null | | continueFromToken(token) | Restores and attaches an event from a continuation token. | token: string | EventLog or null | | getPropagationMetadata(event?, options?) | Produces provider-friendly metadata fields for continuation. | event?: EventLog, options?: PropagationMetadataOptions | PropagationMetadata | | fromMetadata(metadata) | Restores and attaches an event from metadata fields. | metadata: PropagationMetadataInput | EventLog or null | | run(stepName?, fn, options?) | Runs callback with optional step, captures+rethrows errors, optional auto-start/auto-end behavior. | overloads: run(fn, options?), run(stepName, fn, options?) | Promise<T> | | instrument(eventName, fn, options?) | Wraps a function in event lifecycle instrumentation for reuse. | eventName: string, fn: (...args) => T, options?: InstrumentOptions | (...args) => Promise<T> |

RunOptions

| Option | Type | Default | Description | | ------------------ | ------------- | ----------- | ------------------------------------------------------------------ | | failEventOnError | boolean | true | Calls EventFlow.fail(error) before rethrow when callback throws. | | startIfMissing | boolean | false | Auto-starts an event if none is active. | | eventName | string | inferred | Event name used when auto-starting. | | endIfStarted | boolean | true | Auto-ends event only if this run started it. | | statusOnAutoEnd | EventStatus | "success" | Status used for auto-end path. |

EventFlowClientConfigureOptions

| Option | Type | Default | Description | | -------------------- | --------- | ------- | ------------------------------------------------------------------------------ | | showFullErrorStack | boolean | true | When false, failed events include only the first two lines of error.stack. | | branding | boolean | true | When false, ConsoleTransport logs plain JSON without the branding prefix. | | transports | Transport \| Transport[] | n/a | Replaces active transport(s), same as calling setTransport(...). | | encryptionKey | string | n/a | Shared symmetric key used to encrypt encryptedContext during propagation. |

EventFlowClientConfigureWithUserContext<TAccount>

| Option | Type | Default | Description | | -------------------- | ------------------------------------- | -------- | --------------------------------------------------------------------------------------------------- | | showFullErrorStack | boolean | true | Same as EventFlowClientConfigureOptions. | | branding | boolean | true | Same as EventFlowClientConfigureOptions. | | transports | Transport \| Transport[] | n/a | Same as EventFlowClientConfigureOptions; also works alongside getUserContext. | | encryptionKey | string | n/a | Same as EventFlowClientConfigureOptions; encrypts encryptedContext during propagation. | | getUserContext | (account: TAccount) => EventContext | required | Maps your user/account object into the payload used by addUserContext(account) at context.user. |

TransportEmissionOptions

| Option | Type | Default | Description | | -------------------- | ------------------------ | ------- | -------------------------------------------------------------------------------------- | | emissionMode | "all" \| "errors-only" | "all" | Controls whether all events are emitted or only failed events. | | nonErrorSampleRate | number | 100 | Percentage (0-100) of non-failed events to emit. | | debug | boolean | false | When enabled, suppressed successful events trigger a Successful Event debug message. |

InstrumentOptions

InstrumentOptions extends RunOptions and adds:

| Option | Type | Description | | ------------------- | ----------------------------------- | ------------------------------------------------------------------------ | | stepName | string | Step name recorded for each instrumented call (defaults to eventName). | | contextFromArgs | (...args) => EventContext | Adds context from function input arguments. | | contextFromResult | (result, ...args) => EventContext | Adds context from function result. |

Middleware

| Export | Description | Arguments | | --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | eventFlowMiddleware | Default middleware instance using global EventFlow. | (req, res, next) | | createEventFlowMiddleware(client, options?) | Creates middleware around any compatible client (startEvent, addContext, endEvent, fail, fromHeaders). | client: compatible EventFlow client, options?: EventFlowMiddlewareOptions |

EventFlowMiddlewareOptions:

| Option | Type | Default | Description | | ----------------------- | --------------------------- | ----------------------- | ------------------------------------------------------- | | eventName | string or (req) => string | http:${method} ${url} | Event name for non-propagated requests. | | mapContext | (req) => EventContext | undefined | Adds custom request-derived context. | | includeRequestContext | boolean | true | Adds method and url context automatically. | | failOn5xx | boolean | true | Marks event as failed when response status is >= 500. | | autoEnd | boolean | true | Ends events automatically on finish/close. |

Propagation Constants

| Constant | Value | | -------------------------- | ------------------------ | | TRACE_ID_HEADER | "x-eventflow-trace-id" | | EVENT_ID_HEADER | "x-eventflow-event-id" | | CONTEXT_HEADER | "x-eventflow-context" | | ENCRYPTED_CONTEXT_HEADER | "x-eventflow-encrypted-context" | | EVENT_HEADER | "x-eventflow-event" | | EVENTFLOW_TRACE_ID_KEY | "eventflow_trace_id" | | EVENTFLOW_EVENT_ID_KEY | "eventflow_event_id" | | EVENTFLOW_EVENT_NAME_KEY | "eventflow_event_name" | | EVENTFLOW_PARENT_ID_KEY | "eventflow_parent_id" | | EVENTFLOW_CONTEXT_KEY | "eventflow_context" | | EVENTFLOW_ENCRYPTED_CONTEXT_KEY | "eventflow_encrypted_context" |

Exported Types

EventStatus, EventContext, Step, CallerInfo, EventError, EventLog, EventEmissionMode, TransportEmissionOptions, EventFlowClientConfig, EventFlowClientConfigureOptions, EventFlowClientConfigureWithUserContext, UserContextMapper, SerializedPropagationEvent, ContextManager, HeadersLike, RunCallback, RunOptions, InstrumentCallback, InstrumentedFunction, InstrumentOptions, PropagationMetadata, PropagationMetadataInput, PropagationMetadataOptions, EventFlowMiddleware, EventFlowMiddlewareOptions, NodeLikeRequest, NodeLikeResponse, NextFunction.