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

@dwk/log

v0.1.0-beta.2

Published

Minimal injectable structured-logging and metrics seam. Cross-standard reusable; protocol-agnostic, no Workers runtime dependency.

Readme

@dwk/log

Minimal, injectable structured-logging seam shared across the @dwk packages.

A cross-standard reusable lib (like @dwk/dpop and @dwk/rdf): protocol-agnostic, stateless, and unit-testable without a Workers runtime. It defines where the @dwk packages send signal, not how that signal is stored — the composed Worker wires a concrete logger to Workers structured logs / Logpush / Analytics Engine.

See spec/observability.md for the cross-cutting requirement and the event-taxonomy conventions.

Why

The @dwk packages handle untrusted, attacker-supplied input. Without a logging seam, security-relevant events — a blocked SSRF attempt, an auth rejection, a poison queue message — are silently swallowed and indistinguishable from a dead link or a timeout. This package is the seam those events flow through.

The seam

import type { Logger } from "@dwk/log";

export interface Logger {
  debug(event: string, fields?: Record<string, unknown>): void;
  info(event: string, fields?: Record<string, unknown>): void;
  warn(event: string, fields?: Record<string, unknown>): void;
  error(event: string, fields?: Record<string, unknown>): void;
}

Each call names a stable, dotted event (e.g. webmention.ssrf.blocked) plus a flat bag of structured fields, so operators query by event and field instead of grepping prose. Event-name taxonomies are owned by each consuming package.

Usage

A package accepts an optional logger in its config and defaults to noopLogger, so logging is strictly opt-in:

import { noopLogger, type Logger } from "@dwk/log";

function createThing(config: { logger?: Logger }) {
  const logger = config.logger ?? noopLogger;
  logger.warn("thing.blocked", { reason: "policy" });
}

The composed Worker wires a real logger once:

import { consoleLogger } from "@dwk/log";

const logger = consoleLogger({ minLevel: "info", base: { service: "wm" } });
const handler = createWebmention({ baseUrl, logger });

Exports

| Export | Purpose | | ---------------------------- | -------------------------------------------------------------- | | Logger, LogLevel, LogFields | The logging seam types. | | noopLogger | Discards everything; the default when no logger is configured. | | consoleLogger(options?) | Emits one JSON record per call to console (Workers logs). | | withContext(logger, ctx) | Binds request/pod-scoped fields onto every record. | | hostFromUrl(raw) | Redaction helper: a URL's host only, never its path/query. | | Metrics | The metrics seam type (count / observe). | | noopMetrics | Discards everything; the default when no metrics sink is set. | | analyticsEngineMetrics(dataset, options?) | Adapter to Cloudflare Workers Analytics Engine. |

Metrics

The companion metrics seam answers "how often / how much?" for the same events the logger names, so an operator can chart "SSRF blocks/min" or "verification success rate" instead of scraping log lines. It is injected the same way — an optional metrics, defaulting to noopMetrics — and reuses the same event names and field bags as logs, so logs and counters share one vocabulary:

import { noopMetrics, type Metrics } from "@dwk/log";

function createThing(config: { metrics?: Metrics }) {
  const metrics = config.metrics ?? noopMetrics;
  metrics.count("thing.blocked", { reason: "policy" }); // a counter
  metrics.observe("thing.latency", 42, { host: "a.example" }); // an observation
}

The composed Worker wires the real adapter once, from a bound AnalyticsEngineDataset:

import { analyticsEngineMetrics } from "@dwk/log";

// env.WM_METRICS is an AnalyticsEngineDataset binding declared in wrangler.toml.
const metrics = analyticsEngineMetrics(env.WM_METRICS, {
  base: { service: "wm" },
});
const handler = createWebmention({ baseUrl, logger, metrics });

analyticsEngineMetrics maps each call onto writeDataPoint deterministically: the event becomes indexes[0] (the sampling key) and blobs[0]; string fields become further blobs, and numeric/boolean fields become doubles (with a lead 1 for count or the observed value for observe) — all in sorted key order so positions are stable per event. Cloudflare's Analytics Engine limits (1 index ≤ 96 B, ≤ 20 blobs ≤ 16 KB total, ≤ 20 doubles) are enforced, and like Logger a Metrics implementation never throws into the operation it measures. The binding type is declared structurally (AnalyticsEngineDatasetLike), so this package keeps no @cloudflare/workers-types dependency. The same redaction rules apply — never pass tokens, bodies, or full URLs as fields.

Redaction

Redaction is the caller's responsibility, but the seam helps. Never pass tokens, credentials, or full request/response bodies as fields. For URLs, prefer hostFromUrl(raw) so an attacker-supplied path or query string never lands in a log line.