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

@fallow-cli/beacon

v0.4.3

Published

Lightweight runtime coverage beacon for fallow cloud

Readme

@fallow-cli/beacon

Lightweight runtime coverage beacon for fallow cloud. Collects V8 (Node.js) or Istanbul (Node / Bun / Deno / browser) coverage data and sends it to the fallow cloud API for analysis.

Install

npm install @fallow-cli/beacon

Runtime compatibility

| Runtime | Mode | How to enable | | ------- | -------- | -------------------------------------------------------------------------------- | | Node.js | V8 | Set NODE_V8_COVERAGE=<dir> before process start. Zero build change. | | Node.js | Istanbul | Instrument your build with oxc-coverage-instrument or babel-plugin-istanbul. | | Bun | Istanbul | Bun does not implement v8.takeCoverage. Instrument at build time. | | Deno | Istanbul | Instrument at build time. | | Browser | Istanbul | Unchanged. The beacon reads window.__coverage__. |

The beacon auto-detects the active source. It prefers Istanbul when globalThis.__coverage__ is populated (build-time opt-in signals intent and gives higher fidelity than V8's best-effort offsets), and falls back to V8 when NODE_V8_COVERAGE is set. Override with coverageSource: "v8" | "istanbul".

Node.js (V8 coverage)

import { createNodeBeacon } from "@fallow-cli/beacon";

// Must be set before the Node process starts.
// Example shell usage:
// NODE_V8_COVERAGE=/var/tmp/my-app-v8 node server.js
const beacon = createNodeBeacon({
  apiKey: "fallow_live_k1_...",
  projectId: "my-app",
  endpoint: "https://api.fallow.cloud",
});

beacon.start();

process.on("SIGTERM", async () => {
  await beacon.flush();
  await beacon.stop();
  process.exit(0);
});

V8 mode uses best-effort coverage (function-level). Zero CPU overhead. NODE_V8_COVERAGE must be present when the Node process starts; the beacon cannot enable V8 collection retroactively at runtime.

Bun / Deno / Node (Istanbul via build-time instrumentation)

Any Istanbul-compatible instrumenter that writes to globalThis.__coverage__ works. The simplest path for TypeScript projects is oxc-coverage-instrument.

Bun example

// scripts/bun-preload-coverage.ts
import { plugin } from "bun";
import { instrument } from "oxc-coverage-instrument";

plugin({
  name: "oxc-coverage-instrument",
  setup(build) {
    build.onLoad({ filter: /\.(ts|tsx)$/ }, async ({ path }) => {
      const source = await Bun.file(path).text();
      const instrumented = await instrument(source, path);
      return { loader: "ts", contents: instrumented };
    });
  },
});

Preload at startup:

bun --preload ./scripts/bun-preload-coverage.ts run src/index.ts

The beacon then auto-detects Istanbul mode — no code change in your beacon setup:

import { createNodeBeacon } from "@fallow-cli/beacon";

const beacon = createNodeBeacon({
  apiKey: "fallow_live_k1_...",
  projectId: "my-app",
  endpoint: "https://api.fallow.cloud",
  onRuntimeMismatch: (detail) => {
    console.error("beacon cannot capture:", detail);
  },
});

beacon.start();

Instrumented code costs ~1–5% CPU. Gate the preload behind an env var (FALLOW_SERVER_COVERAGE=true) so production deploys without the env var stay zero-overhead.

Ordering requirement: createNodeBeacon(...).start() must be called AFTER your instrumented modules have loaded and populated globalThis.__coverage__. Calling start() in a file that imports nothing, or at the top of the entry point before any await import(...) has run, will detect "no sources available" and fire onRuntimeMismatch. With Bun's --preload, this is the natural order: the preload instruments every file on load, so by the time the entry point's app code runs, globalThis.__coverage__ is already populated by the first instrumented module.

Browser (Istanbul)

import { createBrowserBeacon } from "@fallow-cli/beacon/browser";

const beacon = createBrowserBeacon({
  apiKey: "fallow_live_k1_...",
  projectId: "my-app",
  endpoint: "https://api.fallow.cloud",
  sampleRate: 0.01, // 1% of sessions
});

beacon.start();

Reads window.__coverage__ from Istanbul-instrumented builds. Sends via navigator.sendBeacon() on page visibility change.

Local file capture (transport: "fs")

For trial / offline / air-gapped / CI-sandbox scenarios, the beacon can write each batch as a JSON file to a local directory instead of POSTing to the ingest endpoint. All capture logic (batching, beforeSend, denyPaths, lifecycle hooks, maxQueueSize, retry on transient disk errors) is identical to HTTP mode.

import { createNodeBeacon } from "@fallow-cli/beacon";

const beacon = createNodeBeacon({
  projectId: "my-app",
  transport: "fs",
  writeToDir: "./.fallow-coverage",
  // apiKey and endpoint are ignored in fs mode
});

beacon.start();

process.on("SIGTERM", async () => {
  await beacon.flush();
  await beacon.stop();
  process.exit(0);
});

Each flush produces one file named <timestamp-ms>-<firstPayloadId>.json. The file is written atomically via a .tmp sibling + rename() so readers never see a half-written payload. The directory is created recursively on first flush if it doesn't exist.

File format matches the ingest endpoint's POST body exactly:

{
  "v": 1,
  "batch": [
    {
      "payloadId": "...",
      "projectId": "my-app",
      "environment": "production",
      "commitSha": "...",
      "timestamp": "2026-04-20T10:00:00.000Z",
      "functions": [
        {
          "filePath": "src/index.ts",
          "functionName": "main",
          "lineNumber": 1,
          "hitCount": 5,
          "trackingState": "called"
        }
      ]
    }
  ],
  "clientReports": [
    /* optional; queue_overflow / ratelimit_backoff etc */
  ]
}

v is the payload envelope schema version. 0.3.0 onwards always emits "v": 1. Pre-0.3.0 beacons omit the field (implicit v1); both are accepted by the ingest endpoint. Future breaking changes to the payload shape will bump this number, and consumers that don't understand the new version reject early instead of silently misinterpreting rows.

Consume the directory with fallow health --runtime-coverage-dir (the sidecar handles the JSON shape directly).

Loud failure

The beacon never crashes the host app, but it no longer fails silently either. When no coverage source is available (or the V8 path throws ERR_NOT_IMPLEMENTED on Bun), onRuntimeMismatch fires once with a detail object explaining what was attempted, what failed, and how to fix it. When no callback is supplied, a single message is logged to console.error.

createNodeBeacon({
  // ...
  onRuntimeMismatch: (detail) => {
    logger.error("coverage unavailable", {
      runtime: detail.runtime, // "node" | "bun" | "deno" | "unknown"
      reason: detail.reason, // see table below
      attempted: detail.attempted, // "v8" | "istanbul"
      message: detail.message,
    });
  },
});

onRuntimeMismatch reasons

| reason | When it fires | | :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | no-v8-and-no-istanbul | Neither globalThis.__coverage__ nor NODE_V8_COVERAGE is available. | | v8-takeCoverage-threw | The V8 snapshot loop caught ERR_NOT_IMPLEMENTED (bun) or another unrecoverable error. | | istanbul-global-malformed | globalThis.__coverage__ is set but doesn't have the expected Istanbul shape. | | config-exceeds-server-limits | A caller-supplied projectId, environment, or commitSha is longer than the ingest endpoint will accept. Fired synchronously by createNodeBeacon before the first POST. | | transport-misconfigured | transport: "http" was selected (default) without endpoint/apiKey, or transport: "fs" was selected without writeToDir. Fired synchronously by createNodeBeacon before the first flush. |

commitSha is a git SHA, not a container ref

commitSha is capped at 40 characters (CONFIG_LIMITS.commitSha). That matches a git SHA-1. Fly's FLY_IMAGE_REF (registry.fly.io/<app>:deployment-01..., ~64 chars), Kubernetes' image digests, and similar container references do NOT fit. Passing one of those triggers config-exceeds-server-limits immediately and the beacon returns a no-op. Inject a real git SHA at deploy time instead (e.g. GIT_SHA="$GITHUB_SHA" via your CI workflow's --env).

Configuration

| Option | Type | Default | Max length | Description | | ------------------- | -------------------- | ----------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | projectId | string | required | CONFIG_LIMITS.projectId (200) | Repository/project identifier | | transport | "http" \| "fs" | "http" | n/a | Transport mode. "fs" writes to disk instead of POSTing. | | apiKey | string | required* | n/a | API key (required when transport is "http") | | endpoint | string | required* | n/a | Ingest API URL (required when transport is "http") | | writeToDir | string | required* | n/a | Output directory (required when transport is "fs") | | environment | string | undefined | CONFIG_LIMITS.environment (64) | Deployment environment tag | | commitSha | string | undefined | CONFIG_LIMITS.commitSha (40) | Git commit SHA | | sampleRate | number | 1.0 | n/a | Session sampling rate (browser only, 0–1) | | flushIntervalMs | number | 30000 | n/a | Flush interval in milliseconds | | maxQueueSize | number | 1000 | n/a | Max queued payloads before dropping | | maxBatchSize | number | 100 | n/a | Max payloads per API request / fs file. HTTP requests are also capped at 25,000 function observations; lower this for very large single-file snapshots or smaller retry units. | | enabled | boolean | true | n/a | Kill switch | | coverageSource | "v8" \| "istanbul" | auto | n/a | Override auto-detection | | denyPaths | RegExp[] | [] | n/a | Skip functions matching these paths | | beforeSend | function | undefined | n/a | Transform/redact payload before sending | | onFallback | function | undefined | n/a | Legacy: called when NODE_V8_COVERAGE not set | | onRuntimeMismatch | function | undefined | n/a | Called when coverage cannot be captured in this runtime | | onBudgetWarning | function | undefined | n/a | Called once per warning / danger budget transition | | onBudgetExhausted | function | undefined | n/a | Called when monthly function budget pauses the beacon | | onBudgetSampled | function | undefined | n/a | Called when the server enters 206 sampled mode |

* Requiredness depends on transport: "http" needs apiKey + endpoint; "fs" needs writeToDir. Missing the relevant fields fires onRuntimeMismatch with reason: "transport-misconfigured" and returns a no-op beacon.

Budget callbacks

The ingest API reports monthly-budget pressure through response headers:

  • onBudgetWarning(snapshot) fires once per transition into warning or danger.
  • onBudgetExhausted(snapshot) fires on a 402 response. The HTTP transport pauses until snapshot.resetAt and then resumes automatically.
  • onBudgetSampled(snapshot) fires once per transition into server sampled mode. The server returns 206 Partial Content with x-ingest-budget-state: sampled and x-ingest-budget-sample-rate when the payload cap is exhausted but the function cap still has headroom. The beacon keeps sending, sets transport.state() to "sampled", throttles its flush interval by 1 / snapshot.sampleRate, and reports the estimated dropped payload count as server_sampled in the next clientReports payload.
  • onBatchShrunk(snapshot) fires when the server rejects a request with 413 and the HTTP transport lowers its active maxBatchSize before retrying. No data is dropped when this callback fires; singleton oversized payloads are unretryable and are reported separately as oversized_payload_dropped.

CONFIG_LIMITS is exported from the package entry — import it to validate values before constructing a beacon:

import { CONFIG_LIMITS, createNodeBeacon } from "@fallow-cli/beacon";

if (commitSha.length > CONFIG_LIMITS.commitSha) {
  throw new Error("commitSha must be a 40-char git SHA");
}

Serverless

Auto-detects Lambda, Vercel, and Netlify environments. Switches to per-invocation flush mode automatically.

Minified / bundled code

The beacon reports V8 coverage against the deployed JavaScript. For bundled builds, that means raw bundle paths and offsets.

Source maps are uploaded from CI, not by the beacon. The beacon runs in production and has no access to .map files. Upload them from CI via POST /v1/coverage/:repo/source-maps after the build, keyed by the commit SHA the beacon will report — the ingest pipeline resolves positions to original sources automatically. See the AGENTS.md "Source maps" section for the full workflow.

Known limitations

  • Worker threads (V8 and Istanbul). Each worker has its own isolate and its own globalThis. The main-thread beacon does not see worker coverage. Customers with worker-heavy workloads must ship their own postMessage-based export.
  • Coverage under the node: specifier prefix. Filtered out by default in V8 mode.

License

MIT