@sablecrm/collection-proxy
v0.0.52
Published
A fork of @razaman2/collection-proxy for SableCRM.
Maintainers
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-proxypnpm add @razaman2/collection-proxyTHINGS 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"
}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
111belonging 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'sarray-containsoperator.createdAt: A FirestoreserverTimestamprepresenting when the document was created.updatedAt: A FirestoreserverTimestamprepresenting the last time the document was updated. Automatically updated onupdate()calls.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 pageDirect 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),
});