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

@sablecrm/collection-proxy

v0.0.52

Published

A fork of @razaman2/collection-proxy for SableCRM.

Readme

@razaman2/collection-proxy

A custom firestore client that provides a simple, fluid, and consistent proxy interface for interacting with Google Cloud Firestore across Web, Node.js, and Serverless environments.

Install the dependencies

npm install --save @razaman2/collection-proxy
pnpm add @razaman2/collection-proxy

THINGS YOU SHOULD KNOW

All documents are created with auto-generated metadata outlined by the following object structure:

{
    "belongsTo": ["document-path"],
    "createdAt": "serverTimestamp",
    "updatedAt": "serverTimestamp",
    "id": "document-id"
}
  1. belongsTo: An array that stores all document paths that have a relationship with the current document, including the document's own path.

    Example: A settings document with ID 111 belonging to a user (users/222) and a company (companies/333) will look like: { belongsTo: ["settings/111", "users/222", "companies/333"] }.

    This approach allows querying like: "Get all settings documents that belong to user users/222" using Firestore's array-contains operator.

  2. createdAt: A Firestore serverTimestamp representing when the document was created.
  3. updatedAt: A Firestore serverTimestamp representing the last time the document was updated. Automatically updated on update() calls.
  4. id: Stores the assigned document ID directly on the document data object.

Initializing the Collection

The collection proxy is compatible with both Modular (v9+) and Namespaced / Admin SDK (v8 / Firebase Admin) APIs. Configure static defaults once at your application's entry point.

Namespaced API / Firebase Admin SDK

import firebase from "firebase/compat/app";
import "firebase/compat/firestore";
import { Collection } from "@razaman2/collection-proxy";

firebase.initializeApp({ projectId: "demo-app" });

Collection.setConfig({
    getFirestore: firebase.firestore,
    logging: true, // Enable console logging for operations
});

Modular API (v9+)

import { initializeApp } from "firebase/app";
import { 
    getFirestore, collection, doc, getDoc, getDocs, 
    collectionGroup, onSnapshot, setDoc, updateDoc, deleteDoc, 
    deleteField, arrayUnion, serverTimestamp, writeBatch,
    query, where, orderBy, limit, limitToLast, startAfter, endBefore,
    getCountFromServer, getAggregateFromServer
} from "firebase/firestore";
import { Collection } from "@razaman2/collection-proxy";

initializeApp({ projectId: "demo-app" });

Collection.setConfig({
    modular: true,
    getFirestore: getFirestore(),
    collection,
    doc,
    getDoc,
    getDocs,
    collectionGroup,
    onSnapshot,
    setDoc,
    updateDoc,
    deleteDoc,
    deleteField,
    arrayUnion,
    serverTimestamp,
    writeBatch,
    query,
    where,
    orderBy,
    limit,
    limitToLast,
    startAfter,
    endBefore,
    getCountFromServer,
    getAggregateFromServer,
    logging: true,
});

Current Usage Examples

1. Reading Single Documents

import { Collection } from "@razaman2/collection-proxy";

// Initialize collection proxy and subscribe to a single document realtime
const instance = await Collection.proxy("users").init("user-123");
console.log("Realtime document data:", instance.getData());

// Fetch a single document once (non-realtime)
const userCol = await Collection.proxy("users").getDocument("user-123", { realtime: false });
console.log("Document data:", userCol.getData());

// Access specific field value directly
const userEmail = userCol.getData("email");

2. Reading Collection Documents (Queries)

import { Collection } from "@razaman2/collection-proxy";
import { query, where, orderBy } from "firebase/firestore";

// Read multiple documents with a custom query filter
const collection = await Collection.proxy("users").getDocuments({
    realtime: false,
    query: (ref) => query(ref, where("status", "==", "active"), orderBy("name")),
});

// Returns array of document objects
const activeUsers = collection.getData();
console.log("Active users count:", activeUsers.length);

3. Creating Documents

import { Collection } from "@razaman2/collection-proxy";

// Create document with auto-generated ID
await Collection.proxy("users").create({
    data: { name: "John Doe", email: "[email protected]" },
});

// Create document with explicit ID via setDoc
await Collection.proxy("users")
    .setDoc("user-123")
    .create({
        data: { name: "Jane Doe", email: "[email protected]" },
    });

// Create document with explicit ID via setData
await Collection.proxy("users")
    .setData({ id: "user-123", name: "Jane Doe" })
    .create();

// Create document using path syntax
await Collection.setPath("companies/comp-1/accounts/acc-2").create({
    data: { name: "Primary Account" },
});

4. Updating Documents

import { Collection } from "@razaman2/collection-proxy";

// Update document by explicit ID
await Collection.proxy("users")
    .setDoc("user-123")
    .update({
        data: { status: "active" },
    });

// Update document using path syntax
await Collection.setPath("users/user-123").update({
    data: { lastLogin: new Date() },
});

5. Deleting Documents

import { Collection } from "@razaman2/collection-proxy";

// Delete document by explicit ID
await Collection.proxy("users").setDoc("user-123").delete();

// Delete document with path syntax
await Collection.setPath("users/user-123").delete();

Pagination & Direct Page Navigation

Pagination is powered by the Pagination helper class, providing cursor tracking, page navigation controls (next, prev, start, end, page), and UI metadata output (getMeta).

Pagination Usage

import { Collection, Pagination } from "@razaman2/collection-proxy";
import { query, orderBy } from "firebase/firestore";

// Fetch first page of 10 items using Pagination class
const paginator = await Collection.proxy("products").getDocuments(
    new Pagination(10, {
        realtime: false,
        query: (ref) => query(ref, orderBy("createdAt", "desc")),
    })
);

// Get current page items array
console.log("Page 1 items:", paginator.getData());

// Navigate to Next Page
await paginator.next();
console.log("Page 2 items:", paginator.getData());

// Navigate to Previous Page
await paginator.prev();
console.log("Back to Page 1:", paginator.getData());

// Navigate to Last / First Page
await paginator.end();   // Jump to last page
await paginator.start(); // Jump back to first page

Direct Page Jumping & UI Metadata (getMeta)

const paginator = await Collection.proxy("products").getDocuments(
    new Pagination(10, {
        realtime: false,
        query: (ref) => query(ref, orderBy("createdAt", "desc")),
    })
);

// Jump directly to page 3
await paginator.page(3);

// Generate UI Pagination Meta & Links (Current Page: 3, Total Items: 100)
const meta = paginator.getMeta(3, 100);
console.log(meta);
/*
{
  currentPage: 3,
  nextPage: 4,
  firstPage: 1,
  lastPage: 10,
  perPage: 10,
  total: 100,
  links: [1, 2, 3, 4, 5, 6, 7, 8, "...", 9, 10],
  snapshot: CustomFirestore { ... }
}
*/

Relationships & Nested Subcollections

import { Collection } from "@razaman2/collection-proxy";

// Define nested parent-child relationship
const company = Collection.proxy("companies").setDoc("comp-1");
const account = Collection.proxy("accounts")
    .setParent(company)
    .setDoc("acc-2");

// Verified collection paths
console.log(account.getCollection().path); // "companies/comp-1/accounts"
console.log(account.getDoc().path);        // "companies/comp-1/accounts/acc-2"

// Query documents belonging to parent documents using belongsTo array
const userTasks = await Collection.proxy("tasks").getDocuments({
    realtime: false,
    query: (ref) => query(ref, where("belongsTo", "array-contains", "users/user-123")),
});

Aggregates & Document Counts

import { Collection } from "@razaman2/collection-proxy";
import { sum, average, count } from "firebase/firestore";

// Get total collection document count
const countCol = await Collection.proxy("users").getCollectionCount();
console.log("Total user count:", countCol.getData()); // e.g. 150

// Perform Aggregate queries (Sum, Average, Count)
const aggCol = await Collection.proxy("orders").getCollectionAggregate({
    totalSales: sum("amount"),
    averageSale: average("amount"),
    totalOrders: count(),
});

console.log("Aggregates:", aggCol.getData());
// { totalSales: 4500, averageSale: 150, totalOrders: 30 }

Atomic Batch Writes

import { Collection } from "@razaman2/collection-proxy";
import { writeBatch, getFirestore } from "firebase/firestore";

const batch = writeBatch(getFirestore());

// Queue multiple creation & update operations in a single atomic batch
await Collection.proxy("users").create({
    batch,
    data: { id: "user-1", name: "Alice" },
});

await Collection.proxy("users").create({
    batch,
    data: { id: "user-2", name: "Bob" },
});

await Collection.proxy("settings").update({
    batch,
    data: { id: "app-settings", theme: "dark" },
});

// Commit all batch operations
await batch.commit();

Audit Trail

Attach built-in audit tracking to create, update, and delete operations:

import { Collection, Update } from "@razaman2/collection-proxy";

// Global listener for audit tracking on collection write triggers
Collection.proxy("orders").onWrite({
    callback: (collection) => new Update(collection),
    triggers: ["create", "update", "delete"],
});

// Attach audit trail handler directly to a write call
await Collection.proxy("orders").create({
    data: { id: "order-101", amount: 250 },
    update: (collection) => new Update(collection),
});