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

@apogeelabs/hoppity

v1.0.0

Published

Core hoppity library

Readme

@apogeelabs/hoppity

Contract-driven RabbitMQ topology builder for Node.js microservices, built on Rascal.

Declare your domain contracts, wire up handlers, and let Hoppity derive the entire RabbitMQ topology automatically. No manual topology files. No Rascal config by hand.

Installation

pnpm add @apogeelabs/hoppity
# or
npm install @apogeelabs/hoppity

Requires Node >= 22.

Quick Start

import { z } from "zod";
import hoppity, { defineDomain, onEvent, onCommand } from "@apogeelabs/hoppity";

// 1. Define your domain contracts
const OrdersDomain = defineDomain("orders", {
    events: {
        orderCreated: z.object({ orderId: z.string(), total: z.number() }),
    },
    commands: {
        cancelOrder: z.object({ orderId: z.string() }),
    },
});

// 2. Wire up handlers
const handleOrderCreated = onEvent(
    OrdersDomain.events.orderCreated,
    async (content, { broker }) => {
        console.log("New order:", content.orderId);
    }
);

const handleCancelOrder = onCommand(
    OrdersDomain.commands.cancelOrder,
    async ({ orderId }, { broker }) => {
        await cancelOrder(orderId);
    }
);

// 3. Build the service
const broker = await hoppity
    .service("order-service", {
        connection: { url: "amqp://localhost" },
        handlers: [handleOrderCreated, handleCancelOrder],
        publishes: [OrdersDomain.events.orderCreated],
        logger, // optional — defaults to ConsoleLogger
    })
    .build();

// 4. Use the broker
await broker.publishEvent(OrdersDomain.events.orderCreated, {
    orderId: "ord-123",
    total: 49.99,
});

await broker.shutdown();

Features

  • Contract-driven topologydefineDomain + handlers = all exchanges, queues, bindings, publications, and subscriptions derived automatically
  • Type-safe handlersonEvent, onCommand, onRpc infer content types from Zod schemas at compile time
  • RPC built inbroker.request() / broker.cancelRequest() with correlation IDs, timeouts, and typed responses
  • Middleware pipeline — Cross-cutting concerns (logging, custom topology) via composable middleware
  • Interceptors — Per-message wrappers for telemetry, tracing, metrics, and header injection
  • Schema validation — Optional inbound/outbound Zod validation on every message
  • Escape hatch — Pass raw Rascal BrokerConfig via topology in ServiceConfig for anything that can't be derived

API

Entry Point

import hoppity from "@apogeelabs/hoppity";

const builder = hoppity.service("my-service", config);
const broker = await builder.use(middleware).build();

ServiceConfig

interface ServiceConfig {
    connection: ConnectionConfig;
    handlers?: HandlerDeclaration[];
    publishes?: (EventContract | CommandContract | RpcContract)[];
    interceptors?: Interceptor[]; // per-message wrappers for telemetry, tracing, metrics
    logger?: Logger; // custom logger — replaces ConsoleLogger for entire build pipeline
    topology?: BrokerConfig; // raw Rascal config — merged as base
    instanceId?: string; // auto-generated UUID if omitted
    defaultTimeout?: number; // RPC timeout in ms (default 30_000)
    validateInbound?: boolean; // default true
    validateOutbound?: boolean; // default false
}

ServiceBroker

Returned by .build(). Extends Rascal's BrokerAsPromised with:

  • publishEvent(contract, message, overrides?) — publish a domain event
  • sendCommand(contract, message, overrides?) — send a domain command
  • request(contract, message, overrides?) — make an RPC call
  • cancelRequest(correlationId) — cancel a pending RPC request

Interceptors

Interceptors wrap handler execution (inbound) and publish calls (outbound) on every message. They're the right place for telemetry, tracing, metrics, and header injection — anything that needs to observe or modify message processing at runtime.

interface Interceptor {
    name: string;
    inbound?: InboundWrapper; // wraps event, command, and RPC handler execution
    outbound?: OutboundWrapper; // wraps publishEvent, sendCommand, and request calls
}

Either field is optional — an interceptor can be inbound-only, outbound-only, or both.

Example: handler timing

import hoppity, { Interceptor } from "@apogeelabs/hoppity";

const withHandlerTiming: Interceptor = {
    name: "handler-timing",
    inbound: (handler, meta) => async (payload, ctx) => {
        const start = performance.now();
        try {
            return await handler(payload, ctx);
        } finally {
            console.log(`${meta.contract._name} took ${performance.now() - start}ms`);
        }
    },
};

const broker = await hoppity
    .service("order-service", {
        connection: { url: process.env.RABBITMQ_URL },
        handlers: [handleOrderCreated],
        publishes: [OrdersDomain.events.orderCreated],
        interceptors: [withHandlerTiming],
    })
    .build();

Composition

For interceptors: [A, B], the call chain is A → B → handler → B → A. The first interceptor in the array is the outermost wrapper.

Inbound wrappers receive InboundMetadata — the contract, operation kind ("event" | "command" | "rpc"), service name, and AMQP message headers. Outbound wrappers receive OutboundMetadata — the contract, kind, and service name.

Interceptors vs. middleware

| | Middleware (.use()) | Interceptors | | -------- | ------------------------------------ | ---------------------------------- | | When | Before broker creation | During message processing | | What | Modifies topology, lifecycle hooks | Wraps handler/publish execution | | Scope | Service-level setup | Per-message | | Examples | Custom logger, topology augmentation | Tracing, metrics, header injection |

Interceptor Packages

@apogeelabs/hoppity-open-telemetry provides production-ready withTracing and withMetrics interceptors built on @opentelemetry/api. Both are dual-use: pass them directly as values for default configuration, or call them as factories to supply a custom tracer/meter name or histogram buckets. withTracing handles W3C context propagation across service boundaries automatically — trace context is injected into AMQP headers on publish and extracted on receive, so spans link up without any extra plumbing.

Documentation

  • ReadMe.LLM — complete API reference with all type signatures
  • llms-usage.md — LLM code generation guide for this package

License

ISC