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

@rudderjs/telescope

v15.1.0

Published

Debug assistant for RudderJS — records requests, queries, jobs, exceptions, logs, mail, notifications, events, cache operations, scheduled tasks, model changes, CLI commands, outgoing HTTP requests, authorization decisions, WebSocket lifecycle, Yjs CRDT e

Readme

@rudderjs/telescope

Debug assistant for RudderJS — records requests, queries, jobs, exceptions, logs, mail, notifications, events, cache operations, scheduled tasks, model changes, CLI commands, outgoing HTTP requests, authorization decisions, WebSocket lifecycle, Yjs CRDT events, and dump()/dd() calls.

About the version number. Telescope is currently at v9.x. The high major doesn't reflect 9 ground-up rewrites — it's the result of Changesets peer-bump cascades across the @rudderjs/* workspace, where a major bump in any peer ripples through dependents. The public API has been stable since v6.

Installation

pnpm add @rudderjs/telescope

Setup

Telescope is auto-discovered via defaultProviders(). Just install the package, run pnpm rudder providers:discover, and add a config file:

// config/telescope.ts
import { Env } from '@rudderjs/core'
import type { TelescopeConfig } from '@rudderjs/telescope'

export default {
  enabled: Env.getBool('TELESCOPE_ENABLED', true),
  path: 'telescope',
  storage: 'memory',
  // Real-time dashboard:
  //   'polling' (default) — re-fetch every `pollInterval` ms when Live is on
  //   'stream'            — subscribe to `<path>/api/stream` via EventSource
  updates: 'polling',
  pollInterval: 2000,
} satisfies TelescopeConfig

Real-time updates (SSE)

Flip updates: 'stream' and the dashboard subscribes to a Server-Sent Events endpoint instead of polling. New entries appear the moment they're recorded — no peer dependency, no WebSocket upgrade, no extra auth model. The existing recording toggle and auth gate still apply to the stream endpoint.

Dashboard

Telescope serves a built-in UI at /{path} (default /telescope) with:

  • Dashboard — count cards for all entry types
  • Per-type list pages — searchable, paginated tables with tag filtering and a live-updates toggle (polling by default, SSE-streamed if updates: 'stream')
  • Detail pages — rich, type-specific views at /telescope/{type}/{id} (formatted SQL, mail HTML preview, stack traces, WebSocket timelines, etc.)
  • Batch grouping — entries linked by batchId (e.g. all queries from one request, all events from one WebSocket connection) viewable at /telescope/batches/{batchId}
  • Sensitive data redaction — headers and body fields are redacted at collection time, before they reach storage

The UI is vanilla HTML + Alpine.js + Tailwind CDN — no client framework dependency. Works regardless of whether your app uses React, Vue, Solid, or none.

Telescope Facade

import { Telescope } from '@rudderjs/telescope'

// List entries by type
const requests   = await Telescope.list({ type: 'request', perPage: 25 })
const exceptions = await Telescope.list({ type: 'exception' })
const queries    = await Telescope.list({ type: 'query', search: 'SELECT' })

// Find a single entry
const entry = await Telescope.find('entry-id')

// Count entries
const total       = await Telescope.count()
const jobCount    = await Telescope.count('job')

// Prune entries
await Telescope.prune('log')   // prune by type
await Telescope.prune()        // prune all

| Method | Returns | Description | |--------|---------|-------------| | list(options?) | TelescopeEntry[] | List entries with type/tag/search/pagination filters | | find(id) | TelescopeEntry \| null | Find a single entry by ID | | count(type?) | number | Count entries, optionally filtered by type | | prune(type?) | void | Delete entries, optionally by type | | record(entry) | void | Manually record an entry |

Entry Types

Telescope records 19 entry types via the observer-registry pattern — each peer package exports a process-wide observer singleton, and the corresponding collector subscribes at boot. If a peer package isn't installed, its collector silently skips.

Core watchers

| Type | Collector | Source | Description | |------|-----------|--------|-------------| | request | RequestCollector | @rudderjs/router | Incoming HTTP requests and responses | | query | QueryCollector | @rudderjs/orm | Database queries (flags slow queries above threshold) | | job | JobCollector | @rudderjs/queue | Queue job dispatch and execution | | exception | ExceptionCollector | @rudderjs/core | Unhandled exceptions | | log | LogCollector | @rudderjs/log | Log messages across all channels | | mail | MailCollector | @rudderjs/mail | Sent emails | | notification | NotificationCollector | @rudderjs/notification | Dispatched notifications | | event | EventCollector | @rudderjs/core | Dispatched events with listener list | | cache | CacheCollector | @rudderjs/cache | Cache hits, misses, writes, forgets | | schedule | ScheduleCollector | @rudderjs/schedule | Scheduled task execution with output | | model | ModelCollector | @rudderjs/orm | Model create/update/delete with dirty attributes diff | | command | CommandCollector | @rudderjs/console | CLI command invocations with args, duration, exit code | | http | HttpCollector | @rudderjs/http | Outgoing HTTP requests with timing, headers, response body | | gate | GateCollector | @rudderjs/auth | Authorization decisions — ability, allowed/denied, resolution path (ability/policy/before), timing | | ai | AiCollector | @rudderjs/ai | Agent runs — model, prompt, tool calls, token usage, middleware timing, streamed chunks | | mcp | McpCollector | @rudderjs/mcp | MCP server activity — tool calls, resource reads, prompt renders, server name + timing | | dump | DumpCollector | @rudderjs/support | dump() and dd() calls with arguments and caller location |

Real-time watchers (differentiators — Laravel Telescope doesn't have these)

| Type | Collector | Source | Description | |------|-----------|--------|-------------| | broadcast | BroadcastCollector | @rudderjs/broadcast | Full WebSocket lifecycle — connections, subscriptions, presence, broadcasts, auth failures. Grouped by connectionId. | | sync | SyncCollector | @rudderjs/sync | Yjs CRDT debugging — document open/close, updates applied, awareness changes (throttled), persistence events, sync errors |

Storage Drivers

  • memory (default) — In-process, bounded by maxEntries. Good for development.
  • sqlite — Persistent storage via better-sqlite3. Run pnpm add better-sqlite3 to enable.

Configuration

Every collector can be toggled independently:

// config/telescope.ts
export default {
  enabled: true,
  path: 'telescope',
  storage: 'memory',
  sqlitePath: '.telescope.db',
  maxEntries: 1000,
  pruneAfterHours: 24,

  // Core watchers
  recordRequests: true,
  recordQueries: true,
  recordJobs: true,
  recordExceptions: true,
  recordLogs: true,
  recordMail: true,
  recordNotifications: true,
  recordEvents: true,
  recordCache: true,
  recordSchedule: true,
  recordModels: true,
  recordCommands: true,
  recordHttp: true,
  recordGate: true,
  recordAi: true,
  recordMcp: true,
  recordDumps: true,

  // Real-time watchers
  recordBroadcasts: true,
  recordSync: true,
  syncAwarenessSampleMs: 500,  // throttle Yjs awareness events

  // Filtering
  ignoreRequests: ['/telescope*', '/health'],
  slowQueryThreshold: 100,     // ms

  // Sensitive data redaction (at collection time, never stored)
  hideRequestHeaders: ['authorization', 'cookie', 'set-cookie', 'x-csrf-token', 'x-api-key'],
  hideRequestFields: ['password', 'password_confirmation', 'token', 'secret'],

  // Auth gate for the dashboard
  auth: null,
} satisfies TelescopeConfig

Custom routes registration

For packages or apps that need manual control over telescope routes:

import { registerTelescopeRoutes } from '@rudderjs/telescope'

await registerTelescopeRoutes(storage, {
  path: 'telescope',
  auth: (req) => req.user?.isAdmin ?? false,
  middleware: [myCustomMiddleware],
})

API

The dashboard UI is built on a JSON API that you can use directly to build custom UIs:

| Endpoint | Description | |----------|-------------| | GET /telescope/api/overview | Count per entry type | | GET /telescope/api/{type}s | List entries (supports ?page, ?tag, ?search) | | GET /telescope/api/{type}s/:id | Single entry | | GET /telescope/api/batches/:batchId | All entries in a batch | | DELETE /telescope/api/entries | Prune entries (supports ?type) |

Notes

  • Auto-prune runs on a background interval based on pruneAfterHours.
  • All peer packages are optional — install only the ones you use, and only those collectors activate.
  • createEntry() helper is exported for manually constructing entries.
  • The observer-registry pattern means collectors never break the build if a peer package is absent — they fail gracefully via try/catch on dynamic import.