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

@cccteam/resource-firestore

v0.0.23

Published

The Firestore change feed for live pages over @cccteam/resource

Readme

@cccteam/resource-firestore

The Firestore change feed for live pages over @cccteam/resource. A page that asks for a live list or a live row stays current without polling: the server writes one change document per subscribed user into that user's change set, and this feed is the one listener per tab that reads it and hands each change to the client's live session, which refetches the row or list the change names. It is the only package of the three that depends on the firebase JS SDK; the client stays free of it.

What a page opts into, what it costs, and what it never does are in the client's README under "Live pages". This package adds nothing to that contract; it is one way of delivering the changes, and the client is written so another could replace it.

npm install @cccteam/resource @cccteam/resource-firestore firebase

firebase is a peer dependency: the application installs it once, so one copy of the SDK serves the feed and anything else the application does with Firebase.

Use

Build the feed and hand it to the client's live session once the session is authenticated; stop the session at logout, before the session itself is logged out.

import { firestoreChangeFeed } from '@cccteam/resource-firestore';

const api = createApi({ baseUrl: '/api' });
await api.live.start(firestoreChangeFeed());
// ...
await api.live.stop();

An Angular application provides it instead, and AuthService starts and stops it:

import { CHANGE_FEED } from '@cccteam/resource-angular/types';
import { firestoreChangeFeed } from '@cccteam/resource-firestore';

providers: [{ provide: CHANGE_FEED, useFactory: () => firestoreChangeFeed() }];

What the feed does

start(identity) receives what the server minted for the session's principal at GET <prefix>/live/token: the GCP project, the Firestore database, and either the emulator or a custom token. It initializes a Firebase app of its own for the project (and the web API key, in production), opens the database, and connects as the user: against the emulator, with the SDK's mock user token for the uid (connectFirestoreEmulator(db, host, port, { mockUserToken: { sub: uid, user_id: uid } })); in production, by signing the custom token in through Firebase Auth on an auth held in memory only: the identity is minted for the session and revoked at stop, so nothing of it is written to the browser's storage, and a page load starts with no user to reload. Then it listens to users/{uid}/changes, ordered by at and, once a change has been seen, bounded to the ones after it.

The first snapshot of a listener primes the last change seen and emits nothing. At start, the documents already there predate the page's own first fetch; after an error, the resync the recovery emits already refetches everything. Each later document becomes one ChangeEvent: its kind (row, list, or resource), resource, key, domain, and deleted as the document carries them, and at as unix microseconds in decimal, the version the refetch carries. A document rewritten within its second (the server coalesces writes to one target per second) moves forward and is one more event; a document expiring out of the result set is not.

A quiet stretch is not evidence that anything was missed: the listener keeps its connection and resumes after a drop, and the change documents live ten minutes, so a change is lost only when the listener was down or suspended for longer than that. The feed therefore emits resync on two occasions and no other. When a listener reports an error it delivers nothing more, so the feed starts another after a wait that doubles with each failure (one second, up to thirty), bounded by the last change seen, and the first snapshot of that listener is a resync. And when the page becomes visible again (visibilitychange to visible, or pageshow from the back-forward cache) after having been hidden longer than the change documents live, since browsers throttle or drop a background page's connection, the feed emits a resync. The live session refetches every live row and list by a fresh seed either way, and does the same itself when it starts.

stop() releases the listener, signs the identity out, and deletes the app it initialized. Nothing is remembered across a stop: a later start listens from the beginning again.

The security rules the server leg ships let a user read their own change set and nothing else, and let no client write; the feed never writes.

What is configurable

firestoreChangeFeed(options) takes resyncAfterHidden (how long a page may be hidden before its return is a resync; the change document's life by default), retryDelay and maxRetryDelay (the restart wait), now (the clock), and visibility and page (where the document's visibility and the page's hide and show are heard; the document and the window by default). Every one has a default, and a spec is the usual reason to turn one.

Testing

The specs are written against bun:test and run with bun test projects/resource-firestore/ (the trailing slash keeps bun off the Angular library beside it); bun run typecheck:firestore compiles them with tsc over projects/resource-firestore/tsconfig.spec.json. bun run test:firestore runs both. The specs replace the three SDK modules (firebase/app, firebase/auth, firebase/firestore) with recording stand-ins through mock.module, drive the listener's snapshots and errors by hand, stand in for the document and the window to hide and show the page, and read the events and resyncs back; no Firestore, emulated or real, is involved.