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

@mattdecrevel/loop

v0.8.1

Published

Thin fail-open client for the Loop notification service. Typed Slack-event SDK for routing notifications through one Slack bot, with rich Block Kit rendering server-side.

Readme

@mattdecrevel/loop

Thin, fail-open client for the Loop notification service.

Loop ingests typed notification events over an authenticated HTTP endpoint and routes them to Slack. This client is a zero-runtime-dependency wrapper around the POST /api/events endpoint. It never throws — if Loop is down or the request times out, your application code keeps running.

Install

npm install @mattdecrevel/loop
# or pnpm add / yarn add / bun add

Next.js consumers: this package ships raw TypeScript (no build step). Add it to transpilePackages in your next.config.ts so Next compiles it:

const nextConfig = { transpilePackages: ['@mattdecrevel/loop'] };

Setup (bring your own endpoint)

baseUrl is required — Loop is self-hosted, so you point the client at your own deployment. The recommended pattern is a single shared loopClient module you import everywhere:

// lib/loop.ts
import { Loop } from '@mattdecrevel/loop';

export const loopClient = new Loop({
  apiKey: process.env.LOOP_API_KEY ?? '',
  baseUrl: 'https://loop.decrevel.dev', // your Loop deployment — required
});

apiKey is fail-open: if it's empty the client silently no-ops, so a not-yet- configured environment never throws. baseUrl has no default — you must supply it.

Usage

import { loopClient as loop } from '@/lib/loop';

// Fire-and-forget — never throws.
await loop.notify({
  type: 'contact',
  payload: { name: 'Ada', email: '[email protected]', message: 'Hello!' },
});

// Error sugar.
try {
  doRisky();
} catch (err) {
  await loop.error(err, { route: '/api/checkout', source: 'web' });
}

Typed helpers (recommended)

Per-type sugar over notify() — less boilerplate, harder to misuse. Each helper takes the typed payload + optional LoopExtras (severity, links, footerNote, digest, idempotencyKey, actions, override category).

await loop.signup({ email: '[email protected]', name: 'Ada' });
await loop.signup({ email, name, kind: 'waitlist' });               // waitlist join (renders distinctly)
await loop.subscription({ email, kind: 'new', plan: 'Pro', amount: 29, interval: 'mo', source: 'lemon' });
await loop.subscription({ email, kind: 'addon_cancel', plan: 'Extra seats' });
await loop.feedback({ category: 'bug', message: 'Toggle broken', userEmail, page, consoleErrors });
await loop.cron({ name: 'nightly-sync', ok: true, summary: '42 records' });
await loop.booking({ name, email, start, startIso, location });
await loop.contact({ name, email, message, source: 'form' });
await loop.error(err, { route: '/api/checkout', links: [{ label: 'View in Sentry', url: sentryUrl }] });

// A "free signup" is just a signup routed to the revenue channel — no special kind:
await loop.signup({ email, name }, { category: 'revenue' });

// `generic` and `raw` require an explicit routing category:
await loop.generic({ title: 'Deploy', body: 'shipped' }, 'ops');

Use notify(event) directly when you want full control over the envelope.

Options

| Option | Default | Description | | ----------- | ----------------------------- | ------------------------------------ | | apiKey | (required) | Loop project API key (loop_pk_…). | | baseUrl | (required) | Your Loop deployment's base URL. | | timeoutMs | 3000 | Abort the request after this many ms.|

Fail-open guarantee

Every method swallows errors, logs to console.error, and resolves. Notifications are best-effort — they will never block or break the calling application.

Event types

See ./types (LoopEvent) for the full set of supported event types and their payload shapes: error, signup, subscription, feedback, cron, infra, booking, contact, seo_report, generic, raw.

Every event also accepts these optional base fields:

  • severity'info' | 'warning' | 'error'
  • category — override the default routing category
  • links{ label, url }[], rendered as URL buttons on the message (e.g. "View in Sentry")
  • footerNote — a short context line in the footer (e.g. a budget/spend figure)
  • digest — fold routine info events into a rolling summary (the service batches them and posts a per-category digest on a schedule)
  • idempotencyKey — server drops a repeat event with the same key for the project (deduped at ingest)

Runtime tuples (for dashboards, dropdowns, filters)

The union types above are derived from as const tuples that are also exported, so consumer UIs can iterate the values at runtime without redeclaring them:

import {
  LOOP_CATEGORIES,        // → LoopCategory
  LOOP_SEVERITIES,        // → LoopSeverity
  LOOP_ACTIONS,           // → LoopAction
  LOOP_EVENT_TYPES,       // → LoopEventType
  LOOP_EVENT_STATUSES,    // → LoopEventStatus (server-side delivery status)
} from '@mattdecrevel/loop/types';

<Select>{LOOP_CATEGORIES.map(c => <Option key={c}>{c}</Option>)}</Select>

Changelog

  • 0.7.0Breaking: baseUrl is now required (bring-your-own-endpoint). The previous https://loop.decrevel.dev default was removed. Pass your Loop deployment URL explicitly, e.g. new Loop({ apiKey, baseUrl: 'https://…' }).