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

@bk.cc/workers-telemetry-client

v1.0.0

Published

Lightweight TypeScript client for the Cloudflare Workers Observability Telemetry API

Downloads

61

Readme

workers-telemetry-client

Lightweight, zero-dependency TypeScript client for the Cloudflare Workers Observability Telemetry API.

Install

npm install @bk.cc/workers-telemetry-client

Usage

Client

import { createClient, buildQuery } from "@bk.cc/workers-telemetry-client";

const client = createClient({
  accountId: "your-account-id",
  apiToken: "your-api-token",
});

// Destructuring works — no `this` binding issues
const { queryEvents, listKeys } = createClient({ accountId, apiToken });

Query events

const query = buildQuery()
  .service("my-worker")
  .last("1h")
  .search("error")
  .limit(50)
  .build();

const events = await client.queryEvents(query);

// Or get parsed & deduped log entries in one call
const logs = await client.queryLogs(query);

Time range helpers

buildQuery().last("5m");       // last 5 minutes
buildQuery().last("2h");       // last 2 hours
buildQuery().last("7d");       // last 7 days
buildQuery().lastHour();       // last hour
buildQuery().last24Hours();    // last 24 hours
buildQuery().last7Days();      // last 7 days
buildQuery().last30Days();     // last 30 days
buildQuery().today();          // since midnight
buildQuery().last(3_600_000);  // raw milliseconds still works

Aggregations

const query = buildQuery()
  .service("my-worker")
  .last("1h")
  .view("calculations")
  .count()
  .avg("$workers.wallTimeMs")
  .p95("$workers.wallTimeMs")
  .groupBy("$metadata.service")
  .build();

const response = await client.query(query);

Available calculation methods: count(), countDistinct(key), sum(key), avg(key), min(key), max(key), p50(key), p90(key), p95(key), p99(key).

Query with filters

const query = buildQuery()
  .service("my-worker")
  .timeframe(Date.now() - 3_600_000, Date.now())
  .filter("$metadata.level", "eq", "error")
  .search("timeout", { regex: false, caseSensitive: false })
  .limit(100)
  .build();

const response = await client.query(query);

Pagination

Iterate through large result sets automatically:

const query = buildQuery()
  .service("my-worker")
  .last("24h")
  .limit(100)
  .build();

for await (const batch of client.queryPaginated(query)) {
  console.log(`Got ${batch.length} events`);
  // process batch...
}

Error handling

All errors are thrown as ObservabilityError with a code to distinguish failure types:

import { ObservabilityError } from "@bk.cc/workers-telemetry-client";

try {
  await client.queryEvents(query);
} catch (error) {
  if (error instanceof ObservabilityError) {
    switch (error.code) {
      case "rate_limit":
        console.log(`Rate limited, retry after ${error.retryAfterMs}ms`);
        break;
      case "auth":
        console.log("Check your API token");
        break;
      case "validation":
        console.log("Invalid query:", error.body);
        break;
      case "server":
        console.log("Cloudflare server error");
        break;
    }
  }
}

List available keys

const keys = await client.listKeys();
// [{ key: "$metadata.service", lastSeenAt: 1710000000, type: "string" }, ...]

List values for a key

const values = await client.listValues({
  datasets: ["cloudflare-workers"],
  key: "$metadata.service",
  type: "string",
  timeframe: { from: Date.now() - 86_400_000, to: Date.now() },
});

Parse events into log entries

Raw telemetry events have a deeply nested structure. parseEvent flattens them into a human-friendly WorkerLogEntry:

import { parseEvents, dedup } from "@bk.cc/workers-telemetry-client";

const logs = dedup(parseEvents(events));

for (const log of logs) {
  console.log(`[${log.level}] ${log.service}: ${log.message}`);
}

API

createClient(options)ObservabilityClient

| Method | Returns | Description | |---|---|---| | query(query) | TelemetryQueryResponse | Execute a telemetry query (full response) | | queryEvents(query) | TelemetryEvent[] | Execute a query and return only events | | queryLogs(query) | WorkerLogEntry[] | Execute a query and return parsed log entries | | queryPaginated(query) | AsyncGenerator<TelemetryEvent[]> | Paginate through all matching events | | listKeys() | TelemetryKey[] | List all available telemetry keys | | listValues(request) | TelemetryValue[] | List distinct values for a key |

ClientOptions

| Option | Type | Default | Description | |---|---|---|---| | accountId | string | required | Cloudflare account ID | | apiToken | string | required | API token | | baseUrl | string | https://api.cloudflare.com/client/v4 | Base URL override |

QueryBuilder (via buildQuery())

| Method | Description | |---|---| | .queryId(id) | Set query ID for tracking | | .view(view) | Set view type ("events", "calculations") | | .limit(n) | Max results to return | | .timeframe(from, to) | Set time range (Unix ms) | | .last(duration) | Relative time range ("5m", "1h", "7d", or ms) | | .lastHour() | Last hour preset | | .last24Hours() | Last 24 hours preset | | .last7Days() | Last 7 days preset | | .last30Days() | Last 30 days preset | | .today() | Since midnight today | | .datasets(ds) | Set datasets (default: ["cloudflare-workers"]) | | .service(name) | Filter by Worker service name | | .filter(key, op, value?, type?) | Add a filter | | .filterCombination(combo) | Set filter combination ("AND" / "OR") | | .search(value, opts?) | Search log content | | .count() | Count all events | | .countDistinct(key) | Count distinct values | | .sum(key) | Sum a numeric key | | .avg(key) | Average of a numeric key | | .min(key) / .max(key) | Min / max of a numeric key | | .p50(key) / .p90(key) / .p95(key) / .p99(key) | Percentiles | | .calculate(op, key?) | Add any calculation | | .calculations(calcs) | Set calculations directly | | .groupBy(key, opts?) | Add a group-by clause | | .groupBys(groups) | Set group-by clauses directly | | .build() | Build the TelemetryQuery object |

ObservabilityError

| Property | Type | Description | |---|---|---| | code | ErrorCode | "auth", "validation", "rate_limit", "server", "unknown" | | status | number | HTTP status code | | body | string | Response body | | retryAfterMs | number \| null | Parsed retry-after (rate limit only) |

Utilities

| Function | Description | |---|---| | parseEvent(event) | Parse a raw event into a WorkerLogEntry | | parseEvents(events) | Parse an array of events | | dedup(logs) | Deduplicate log entries by request ID | | parseDuration(str) | Parse "5m", "1h", "7d" into milliseconds |

License

MIT