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

@ttt-productions/notification-core

v0.15.3

Published

Shared notification system for TTT Productions apps — active/history two-tier architecture with dedup, batch processing, and themed UI components

Readme

@ttt-productions/notification-core

Generic notification system for TTT Productions apps — a two-tier active → history model with dedup, batch processing, and themed React UI.

Entrypoints

  • @ttt-productions/notification-core — root-exported shared types.
  • @ttt-productions/notification-core/react — React hooks/components.
  • @ttt-productions/notification-core/server — backend helper utilities.

Backend/Functions code should avoid the /react subpath.

Model

A two-tier active → history model. There is no isRead flag; instead, active docs carry a seenAt field (0 = unseen). Opening the tray marks items seen (the consuming app stamps seenAt via markNotificationSeenWithGeneration), which clears the unread badge without archiving — so "seen" and "dismissed" are distinct states. Archiving is the explicit dismiss: it moves a notification from the active collection to history. The history doc is a wrapper — the archived notification is nested under archivedSnapshot, alongside archive metadata and an app-supplied expireAt Timestamp on the history doc to back native TTL.

Duplicate triggers for the same dedupKey increment a single active doc's count, append the actor, and reset seenAt to 0 so new activity re-lights the badge.

Notifications are Cloud-Functions-only — clients never write notification docs. The archive React hooks (useArchiveNotification / useArchiveAllNotifications) perform no Firestore writes; they take an app-supplied archiveFn / archiveAllFn adapter wired to the app's callable (e.g. httpsCallable(functions, 'archiveNotification')) and invalidate the read keys on success. On the server, the app's callable enforces ownership (personal: targetUserId === callerUid; shared: caller must be admin) and then runs the active → history move through archiveNotificationWithGeneration, which keys the history doc on a retry-stable, app-built deterministic id and only archives when the card's observed activityGeneration still matches.

Identity is id-only

Shared notification docs persist actor ids onlyNotificationDoc exposes latestActorIds, and PendingNotification exposes actorId. There are no persisted display names (no latestActorNames / actorName); names are resolved at read time by the consuming app (e.g. from publicUsers) so they never drift in the stored doc.

Usage

import { useActiveNotifications } from '@ttt-productions/notification-core/react';

const { data: notifications, isLoading } = useActiveNotifications({
  config: TTT_NOTIFICATION_CONFIG,
  userId: currentUser.uid,
  category: 'user',
});

Backend creation goes through the delivery ledger (id-only — pass actorId, never a name). The app writes a reliable-occurrence/delivery row via createDeliveryLedger and the global materializer converges per-recipient active cards on the deterministic buildActiveNotificationDocId; seen/archive run through the observed-generation helpers (markNotificationSeenWithGeneration / archiveNotificationWithGeneration).

import { createDeliveryLedger } from '@ttt-productions/notification-core/server';

const ledger = createDeliveryLedger(db, TTT_NOTIFICATION_CONFIG);
await ledger.enqueue([
  { deliveryId, notificationType, eventId, recipientUid, aggregationKey, strategy, payload, payloadVersion, materializationClass },
]);