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 🙏

© 2025 – Pkg Stats / Ryan Hefner

vitest-probe

v0.0.8

Published

A tree-shakable probe statement for vitest

Readme

vitest-probe

A tiny probe you can drop into your codebase to observe internals in tests—without breaking encapsulation or shipping debug code to production.

Just sprinkle // #probe('label', expr) comments in your code where you want visibility.

Then in tests, use getProbe({ … }) to read a scoped, timeout-safe stream of those emissions.

  • Parallel-safe via AsyncLocalStorage: each test can isolate its own scope with probe.run(...).
  • Zero prod overhead & deps: the directive is enabled only in tests; no runtime import is needed in app code.

Why?

Unit tests often need to “peek” at intermediate values or call non-public helpers. Exposing internals just for tests or writing elaborate harnesses creates maintenance drag.

This library gives you assertion-like tracepoints that:

  • stay purely observational
  • are scoped to the current test
  • are compiled away in production bundles

Install

npm i -D vitest-probe
# or
pnpm add -D vitest-probe
# or
yarn add -D vitest-probe

Requires Node 16+ (uses AsyncLocalStorage). Designed for Vitest/Jest (Node env).


Quick start

1) Enable the directive in Vitest

// vitest.config.ts
import { defineConfig } from 'vitest/config'
import { probeDirective } from 'vitest-probe'

export default defineConfig({
  test: { environment: 'node' },
  plugins: [probeDirective.vite()],
})

2) Instrument your code with directive comments

// src/my-service.ts
export class MyService {
  async doStuff(n: number) {
    const mid = n * 2
    // #probe('mid', mid)

    await new Promise(r => setTimeout(r, 5))

    const done = mid + 1
    // #probe('done', done)
    return done
  }
}

3) Consume emissions in tests (parallel-safe)

// tests/my-service.test.ts
import { it, expect } from 'vitest'
import { MyService } from '../src/my-service'
import { getProbe } from 'vitest-probe'

it.concurrent('emits scoped values', async () => {
  const svc = new MyService()
  const probe = getProbe<{ label: 'mid'|'done'; value: number }>({ timeoutMs: 200 })

  await probe.run(async () => {
    const p = svc.doStuff(10)
    expect(await probe.next()).toEqual({ label: 'mid',  value: 20 })
    expect(await probe.next()).toEqual({ label: 'done', value: 21 })
    await p
  })

  probe.dispose()
})

Each getProbe() instance uses a unique AsyncLocalStorage scope; only emissions produced within its probe.run(...) block are delivered to that probe—so it.concurrent(...) stays clean.


API

Directive: // #probe(label, value)

Adds a probe emission at build time (only when the directive plugin is active, e.g., in tests). No imports required in your app code.

Tips

  • Treat labels as a typed union in your module for refactor safety:

    export type ParseLabel = 'parse:tokens'|'parse:ast'|'parse:done'
  • Avoid emitting secrets/PII. Redact if needed.


getProbe(options?): Probe

Create a probe bound to a unique async scope.

Options:

  • timeoutMs?: number – default timeout for next() (default 1000ms).
  • filter?: (e) => boolean – per-probe filter for events.
  • bufferSize?: number – max buffered events (100). Oldest are dropped if exceeded.

Returns a Probe:

await probe.next(timeoutMs?)

Resolves with the next { label, value } emitted within the probe’s scope. Rejects on timeout.

const e = await probe.next()       // uses default timeout
const e2 = await probe.next(500)   // override per call

probe.run(fn)

Run fn inside the probe’s AsyncLocalStorage scope. Only emissions within this call chain are delivered to this probe.

await probe.run(async () => {
  await svc.doWork()
})

Do not nest different probes’ run() around the same code region; only the innermost scope receives events.

probe[Symbol.asyncIterator]()

Use as an async iterator (infinite; prefer next() with explicit expectations):

for await (const e of probe) {
  // break once you’ve asserted what you need
  break
}

probe.dispose()

Unsubscribe and reject any pending next() calls. Call in afterEach() to avoid leaks.


Patterns & examples

Filter by label

const probe = getProbe({ filter: e => e.label === 'ast', timeoutMs: 200 })
await probe.run(async () => {
  await parser.parse('a,b,c')
  expect(await probe.next()).toEqual({ label: 'ast', value: expect.any(Object) })
})
probe.dispose()

Parallel tests

it.concurrent('A', async () => {
  const probe = getProbe({ timeoutMs: 200 })
  await probe.run(async () => {
    await svc.doStuff(10)
    expect(await probe.next()).toEqual({ label: 'mid', value: 20 })
  })
  probe.dispose()
})

it.concurrent('B', async () => {
  const probe = getProbe({ timeoutMs: 200 })
  await probe.run(async () => {
    await svc.doStuff(5)
    expect(await probe.next()).toEqual({ label: 'mid', value: 10 })
  })
  probe.dispose()
})

Typed labels, end-to-end

// src/math.ts
export type MathLabel = 'integrate:area'|'integrate:sum'
export function integrate(...) {
  __TEST__ && probeEmit<MathLabel, unknown>('integrate:sum', acc)
}
// tests/math.test.ts
const probe = getProbe<{ label: MathLabel; value: number }>()

TypeScript types

This package is written in TypeScript and ships types. You can narrow labels via generics:

const probe = getProbe<{ label: 'ast'|'done'; value: unknown }>()

Advanced configuration

By default #probe directive is transformed to __PROBE__('mid', mid) at build time, where __PROBE__ is a named import for probeEmit.

You can configure the transformation with the probeDirective plugin:

import { defineConfig } from 'vitest/config'
import { probeDirective } from 'vitest-probe'

export default defineConfig({
  test: { environment: 'node' },
  define: { __TEST__: true }, // enables probe during tests
  plugins: [probeDirective.vite({
    probeIdent: '__PROBE_EMIT__', //code will be transformed to __PROBE_EMIT__('foo', bar)
    directive: '#observe', // parser will look for #observe('foo', bar)
    include: 'src/controllers',
    exclude: 'src/controllers/health',
    keepDefaultExcludes: false, //dont exclude node_modules and virtual modules, defaults to true
  })],
})

License

MIT © Nicola Dal Pont