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

@soft-where/meter

v1.0.3

Published

SOFT-WHERE usage metering library — production-safe, edge-compatible, stateless

Readme

@soft-where/meter

Production-safe, edge-compatible usage metering for Node and Next.js. Tracks API requests, bandwidth, blob storage, custom metrics, and DB snapshots by posting to the SOFT-WHERE ERP. Stateless, non-blocking, and only active when NODE_ENV === "production".

Install

npm install @soft-where/meter

Required variables

The meter needs three values. Pass them in when calling createMeter(); in apps you typically read from environment variables so secrets stay out of code.

| Variable | Purpose | Example | |----------|---------|--------| | projectId | Your SOFT-WHERE project identifier | "proj_abc123" | | apiKey | Secret key for the ERP (do not expose to the client) | From SOFT-WHERE dashboard | | erpUrl | Full URL of the SOFT-WHERE ERP ingest endpoint | "https://erp.soft-where.com/ingest" | | environment | Optional label (e.g. staging vs production) | "production" | | development | Optional. When true, logs every meter operation (success, failure, and skipped) to the terminal. | true in dev, omit or false in production |

Important: Do not use NEXT_PUBLIC_* for apiKey or other secrets. Use server/edge-only env vars (e.g. SOFT_WHERE_API_KEY) so they are not bundled for the browser.

Usage

1. Create a meter

Call createMeter() once (e.g. at module load or in a shared helper) and reuse the returned object. Use env vars for config so you can change them per environment without code changes.

import { createMeter } from "@soft-where/meter";

const meter = createMeter({
  projectId: process.env.SOFT_WHERE_PROJECT_ID!,
  apiKey: process.env.SOFT_WHERE_API_KEY!,
  erpUrl: process.env.SOFT_WHERE_ERP_URL!,
  environment: process.env.VERCEL_ENV ?? process.env.NODE_ENV,
  development: process.env.NODE_ENV !== "production", // verbose logging in dev
});

2. Use the meter

  • trackRequest(req, res, durationMs, bandwidthBytes?) — Call with the same Request and Response (or Next.js NextRequest / NextResponse) and the request duration in milliseconds. The library sends one request event. By default it reads bandwidth from Response#headers Content-Length; that header is often missing in Next.js middleware (because the response body is produced after middleware runs), so bandwidth is then reported as 0. Pass the optional fourth argument bandwidthBytes when you know the response size (e.g. from a route handler or response wrapper) so the ERP gets accurate bandwidth.
  • trackBlobUpload(bytes) / trackBlobDelete(bytes) — Call from upload/delete handlers with the byte deltas.
  • trackCustomMetric(name, value) — Send arbitrary numeric metrics.
  • attemptDbSnapshot(getDbSize) — Call with an async function that returns DB size in bytes (e.g. Neon HTTP). The library will call it, then post the snapshot to the ERP without blocking on the network.

All methods are no-ops when NODE_ENV !== "production". They never throw and never block request completion (ERP calls are fire-and-forget with keepalive: true).

Tracking bandwidth: The library reports bandwidth from the response Content-Length header when present. In Next.js middleware the response from NextResponse.next() is created before the body exists, so Content-Length is usually missing and bandwidth is 0. To report accurate bandwidth, pass the response body size as the fourth argument: trackRequest(req, res, durationMs, responseBodyBytes). You can supply this from API routes that know their payload size, or from any layer that measures the outgoing response (e.g. a wrapper that counts bytes).

Development mode (verbose logging)

Pass development: true in createMeter() to enable verbose terminal logging for every meter operation:

  • ✓ success — Payload was sent and the ERP responded with success (e.g. 2xx).
  • ✗ failure — Send failed (network error, non-2xx response, or exception).
  • ○ skipped — Operation was not sent because NODE_ENV !== "production".

Use this in local or staging to confirm the meter is being called and that the ERP is reachable. Leave development unset or false in production to avoid log noise.

const meter = createMeter({
  projectId: process.env.SOFT_WHERE_PROJECT_ID!,
  apiKey: process.env.SOFT_WHERE_API_KEY!,
  erpUrl: process.env.SOFT_WHERE_ERP_URL!,
  development: process.env.NODE_ENV !== "production",
});

Next.js: use in middleware.ts

Middleware runs on the Edge runtime. You can use @soft-where/meter there to send request counts and duration to the SOFT-WHERE ERP. Bandwidth: in middleware, the response from NextResponse.next() usually has no Content-Length yet (the body is generated later), so bandwidth is reported as 0 unless you pass it explicitly (see below).

Cursor agent instructions (Next.js middleware integration)

Use the following as both documentation and a prompt for a Cursor agent that should wire this package into a Next.js app’s middleware.

Goal: Integrate @soft-where/meter into the existing Next.js middleware so that every request is metered and reported to the SOFT-WHERE ERP. Do not break existing middleware logic (auth, redirects, etc.); add metering alongside it.

Steps:

  1. Install the package

    • Add dependency: @soft-where/meter.
  2. Environment variables

    • Ensure the Next.js project has these server/edge-only env vars (e.g. in .env.local or in the hosting provider’s dashboard). Do not prefix them with NEXT_PUBLIC_:
      • SOFT_WHERE_PROJECT_ID — SOFT-WHERE project ID.
      • SOFT_WHERE_API_KEY — Secret API key for the ERP.
      • SOFT_WHERE_ERP_URL — Full ERP ingest URL (e.g. https://erp.soft-where.com/ingest).
    • Document these in the project’s env example (e.g. .env.example) with short comments.
  3. Create a shared meter instance

    • In the same file as the middleware (or in a small module it imports), create the meter once using the env vars above:
      • createMeter({ projectId, apiKey, erpUrl, environment?, development? }).
      • Read projectId, apiKey, and erpUrl from process.env.SOFT_WHERE_PROJECT_ID, process.env.SOFT_WHERE_API_KEY, and process.env.SOFT_WHERE_ERP_URL.
      • Optionally set development: process.env.NODE_ENV !== "production" to get verbose success/failure logging in the terminal during development.
      • Only create/call the meter when these env vars are defined so the app does not crash in dev or when the integration is disabled.
  4. Wire metering into middleware.ts

    • In the middleware function:
      • Record the start time at the very beginning (e.g. const start = Date.now()).
      • Run the existing middleware logic and obtain the NextResponse (or other response) you intend to return.
      • Before returning, call meter.trackRequest(request, response, durationMs) or meter.trackRequest(request, response, durationMs, bandwidthBytes) where:
        • request is the middleware’s NextRequest (it is a standard Request).
        • response is the NextResponse (or response) you are about to return (it is a standard Response).
        • durationMs is Date.now() - start.
        • bandwidthBytes (optional): in middleware, the response often has no Content-Length, so bandwidth defaults to 0. Pass the actual response body size here if you have it (e.g. from a header you set elsewhere or from measuring the response). Otherwise omit it.
      • Return the same response as before so behavior is unchanged.
    • Do not await the meter; trackRequest is fire-and-forget and must not block the response.
  5. Edge compatibility

    • Middleware runs on the Edge runtime. @soft-where/meter is edge-compatible and uses the global fetch with keepalive: true. No extra config is required.
  6. Production-only behavior

    • The library only sends data when NODE_ENV === "production". In development, all meter calls are no-ops; no need to guard calls in code.

Summary for the agent: Install @soft-where/meter, add env vars SOFT_WHERE_PROJECT_ID, SOFT_WHERE_API_KEY, and SOFT_WHERE_ERP_URL (no NEXT_PUBLIC_), create one meter with createMeter() from those env vars, then in middleware.ts measure duration and call meter.trackRequest(request, response, durationMs) before returning the response. Optionally pass a fourth argument bandwidthBytes if the app can provide response body size (otherwise bandwidth is 0 in middleware). Leave all other middleware logic intact.


Minimal middleware example

// middleware.ts (Next.js)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { createMeter } from "@soft-where/meter";

const projectId = process.env.SOFT_WHERE_PROJECT_ID;
const apiKey = process.env.SOFT_WHERE_API_KEY;
const erpUrl = process.env.SOFT_WHERE_ERP_URL;

const meter =
  projectId && apiKey && erpUrl
    ? createMeter({ projectId, apiKey, erpUrl })
    : null;

export function middleware(request: NextRequest) {
  const start = Date.now();
  const response = NextResponse.next();
  const durationMs = Date.now() - start;

  if (meter) {
    meter.trackRequest(request, response, durationMs);
  }

  return response;
}

API reference

  • createMeter(options)
    options.projectId (string), options.apiKey (string), options.erpUrl (string), options.environment? (string), options.development? (boolean). When development is true, all meter operations are logged to the terminal (success, failure, and skipped).
    Returns a Meter object.

  • meter.trackRequest(req, res, durationMs, bandwidthBytes?)
    req and res are the Web API Request and Response (or Next.js equivalents). durationMs is the request duration in milliseconds. Sends one request event. Bandwidth is taken from the optional bandwidthBytes argument if provided and ≥ 0; otherwise from res.headers Content-Length (often 0 in Next.js middleware). Pass bandwidthBytes when you know the response body size so the ERP gets accurate bandwidth.

  • meter.trackBlobUpload(bytes) / meter.trackBlobDelete(bytes)
    bytes is the size delta.

  • meter.trackCustomMetric(name, value)
    name (string), value (number).

  • meter.attemptDbSnapshot(getDbSize)
    getDbSize is a () => Promise<number> (e.g. Neon HTTP size). The library calls it and posts the result to the ERP without awaiting the network. Returns a Promise that resolves when the size has been obtained and the send has been triggered (or on error); never throws.

License

MIT