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

@idevconn/push-notifications

v1.0.0

Published

Host-agnostic push notifications: Web Push/VAPID, FCM, APNs sending, a NestJS module, and React 19 hooks.

Readme

@idevconn/push-notifications

Host-agnostic push notifications for Nest 11 + React 19: Web Push (VAPID), FCM, and APNs sending, subscription management, and notification history.

Install

npm install @idevconn/push-notifications
# plus whichever providers you use:
npm install web-push        # Web Push
npm install firebase-admin   # FCM
npm install @parse/node-apn  # APNs
npm install typeorm @nestjs/typeorm  # optional TypeORM storage adapter
npm install firebase         # FCM web token hook (client)

Server (Nest 11)

import { PushNotificationModule } from '@idevconn/push-notifications/server';

@Module({
  imports: [
    PushNotificationModule.forRoot({
      webpush: { vapidPublicKey, vapidPrivateKey, subject: 'mailto:[email protected]' },
      fcm: { serviceAccount },
      apns: { key, keyId, teamId, bundleId },
      pushTargetStore: myStore, // implements PushTargetStore
    }),
  ],
})
export class AppModule {}

React 19

import { usePushPermission, usePushSubscription } from '@idevconn/push-notifications/react';

TypeORM adapter

import { PushTargetEntity, TypeOrmPushTargetStore } from '@idevconn/push-notifications/typeorm';

Custom storage (Supabase, MongoDB, raw Postgres/MySQL, etc.)

The TypeORM adapter above is just one pre-built option. PushTargetStore and NotificationLogStore are plain interfaces — this package has no opinion on your database. Implement the four methods against whatever client you already use and pass it as pushTargetStore/notificationLogStore.

interface PushTargetStore {
  save(userId: string, target: PushTarget): Promise<void>;
  findByUserId(userId: string): Promise<PushTarget[]>;
  delete(userId: string, target: PushTarget): Promise<void>;
  findAll(): Promise<PushTarget[]>;
}

Supabase (table push_subscriptions(user_id text, target jsonb)):

import type { SupabaseClient } from '@supabase/supabase-js';
import type { PushTargetStore, PushTarget } from '@idevconn/push-notifications';

export class SupabasePushTargetStore implements PushTargetStore {
  constructor(private readonly supabase: SupabaseClient) {}

  async save(userId: string, target: PushTarget): Promise<void> {
    await this.supabase.from('push_subscriptions').insert({ user_id: userId, target });
  }

  async findByUserId(userId: string): Promise<PushTarget[]> {
    const { data } = await this.supabase.from('push_subscriptions').select('target').eq('user_id', userId);
    return (data ?? []).map((row) => row.target as PushTarget);
  }

  async delete(userId: string, target: PushTarget): Promise<void> {
    await this.supabase.from('push_subscriptions').delete().eq('user_id', userId).contains('target', target);
  }

  async findAll(): Promise<PushTarget[]> {
    const { data } = await this.supabase.from('push_subscriptions').select('target');
    return (data ?? []).map((row) => row.target as PushTarget);
  }
}

// PushNotificationModule.forRoot({ ..., pushTargetStore: new SupabasePushTargetStore(supabase) })

MongoDB (collection pushSubscriptions with { userId, target } documents):

import type { Collection } from 'mongodb';
import type { PushTargetStore, PushTarget } from '@idevconn/push-notifications';

export class MongoPushTargetStore implements PushTargetStore {
  constructor(private readonly collection: Collection<{ userId: string; target: PushTarget }>) {}

  async save(userId: string, target: PushTarget): Promise<void> {
    await this.collection.insertOne({ userId, target });
  }

  async findByUserId(userId: string): Promise<PushTarget[]> {
    const docs = await this.collection.find({ userId }).toArray();
    return docs.map((doc) => doc.target);
  }

  async delete(userId: string, target: PushTarget): Promise<void> {
    await this.collection.deleteOne({ userId, target });
  }

  async findAll(): Promise<PushTarget[]> {
    const docs = await this.collection.find({}).toArray();
    return docs.map((doc) => doc.target);
  }
}

Raw Postgres/MySQL (pg/mysql2, table with a target JSON column): same four methods — save is an INSERT, findByUserId a SELECT ... WHERE user_id = ?, delete a DELETE ... WHERE user_id = ? AND target = ? (compare on the JSON column or an extracted token/endpoint column), findAll a plain SELECT. NotificationLogStore follows the identical pattern for whichever store you pick — implement save/findUnreadByUserId/markAsRead the same way.

See docs/superpowers/specs/2026-09-07-push-notification-design.md for full design rationale.