Package Exports
- @ilniqjs/firestore-adapter
- @ilniqjs/firestore-adapter/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@ilniqjs/firestore-adapter) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
๐ฅ @zyljs/firestore-adapter
Enterprise-grade Firestore database adapter for serverless applications with multi-database management, 100+ operations, connection pooling, automatic retries, and advanced relational capabilities.
โจ Features
๐ Core Features
- โ 100+ Database Operations - CRUD, Relational, Batch, Transactions, Migrations
- โ Multi-Database Manager - MongoDB-style instance management for multiple Firestore projects
- โ Serverless-Optimized - Connection pooling, lazy loading, auto-reconnect
- โ Works Everywhere - Vercel, AWS Lambda, Cloud Functions, Express, Next.js
- โ
Relational Queries -
{data, refs}pattern for clean entity relationships - โ Automatic Retries - Exponential backoff for transient failures
- โ Full TypeScript - Complete type safety and IntelliSense support
- โ Production-Ready - Battle-tested in production environments
๐ฏ Advanced Features
- ๐ Connection Pooling - Reuse connections across serverless invocations
- ๐ Health Monitoring - Built-in metrics and health checks
- ๐ Multi-Tenant Support - Easy management of tenant-specific databases
- ๐ก๏ธ Error Handling - Comprehensive error types and retry logic
- ๐ Query Builder - Simple and advanced query patterns
- ๐ Migrations - Schema transformation and batch operations
- ๐พ Data Sanitization - Automatic Firestore data cleaning
๐ฆ Installation
npm install @zyljs/firestore-adapter firebase-admin๐ Quick Start
Single Database
import { FireStoreDBAdapter } from "@zyljs/firestore-adapter";
// Initialize
const db = new FireStoreDBAdapter({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY,
});
// Use it
await db.create("users", { name: "Alice", email: "alice@example.com" });
const user = await db.read("users", userId);
await db.update("users", userId, { age: 26 });Multiple Databases (Recommended)
import { DatabaseManager } from "@zyljs/firestore-adapter";
// Create multiple database instances
const mainDB = DatabaseManager.createInstance("main", {
projectId: process.env.MAIN_PROJECT_ID,
clientEmail: process.env.MAIN_CLIENT_EMAIL,
privateKey: process.env.MAIN_PRIVATE_KEY,
});
const analyticsDB = DatabaseManager.createInstance("analytics", {
projectId: process.env.ANALYTICS_PROJECT_ID,
clientEmail: process.env.ANALYTICS_CLIENT_EMAIL,
privateKey: process.env.ANALYTICS_PRIVATE_KEY,
});
// Use them
await mainDB.create("users", { name: "Alice" });
await analyticsDB.create("events", { type: "signup" });
// Or get by name anywhere
const db = DatabaseManager.getInstance("main");
await db.read("users", userId);๐ Documentation
1. Database Manager (Multi-Database)
The DatabaseManager allows you to manage multiple Firestore projects, similar to MongoDB connection management.
Create Instances
import { DatabaseManager } from "@zyljs/firestore-adapter";
// Create instances
const usersDB = DatabaseManager.createInstance("users", {
projectId: "users-project",
clientEmail: process.env.USERS_EMAIL,
privateKey: process.env.USERS_KEY,
});
const ordersDB = DatabaseManager.createInstance("orders", {
projectId: "orders-project",
clientEmail: process.env.ORDERS_EMAIL,
privateKey: process.env.ORDERS_KEY,
});
const logsDB = DatabaseManager.createInstance("logs", {
projectId: "logs-project",
clientEmail: process.env.LOGS_EMAIL,
privateKey: process.env.LOGS_KEY,
});Get Instances
// Get specific instance
const db = DatabaseManager.getInstance("users");
// Get default instance (first one created)
const defaultDB = DatabaseManager.getDefault();
// Set custom default
DatabaseManager.setDefault("users");Instance Management
// Check if instance exists
const exists = DatabaseManager.hasInstance("users");
// Get all instance names
const names = DatabaseManager.getInstanceNames(); // ["users", "orders", "logs"]
// Get instance count
const count = DatabaseManager.getInstanceCount(); // 3
// Close specific instance
await DatabaseManager.closeInstance("logs");
// Close all instances
await DatabaseManager.closeAll();Health & Metrics
// Health check all instances
const health = DatabaseManager.healthCheck();
/*
{
healthy: true,
totalInstances: 3,
defaultInstance: "users",
instances: {
users: { connected: true, operations: 1234, idleTime: "5s" },
orders: { connected: true, operations: 567, idleTime: "10s" },
logs: { connected: true, operations: 890, idleTime: "2s" }
}
}
*/
// Get metrics for all instances
const allMetrics = DatabaseManager.getAllMetrics();
// Get metrics for specific instance
const userMetrics = DatabaseManager.getInstanceMetrics("users");2. CRUD Operations
Create
// Create with auto-generated ID
const result = await db.create("users", {
name: "Alice",
email: "alice@example.com",
age: 25,
});
console.log(result.id); // "abc123"
// Create with specific ID
await db.set("users", "user_1", {
name: "Bob",
email: "bob@example.com",
});
// Create with merge
await db.set("users", "user_1", { age: 30 }, true);Read
// Read single document
const user = await db.read("users", "user_1");
console.log(user); // { id: "user_1", name: "Bob", age: 30 }
// Read returns null if not found
const notFound = await db.read("users", "invalid_id"); // null
// Check if exists
const exists = await db.exists("users", "user_1"); // true
// List all documents
const allUsers = await db.list("users");
// Count documents
const count = await db.count("users");Update
// Update document
await db.update("users", "user_1", {
age: 31,
lastLogin: new Date(),
});
// Upsert (create if not exists, update if exists)
await db.upsert("users", "user_1", {
name: "Charlie",
email: "charlie@example.com",
});Delete
// Delete single document
await db.delete("users", "user_1");
// Delete returns confirmation
const result = await db.delete("users", "user_1");
console.log(result); // { id: "user_1", deleted: true }3. Relational Operations
Perfect for entities with relationships (comments โ posts, likes โ users, etc.)
Create with Relationships
// Create post
const post = await db.createRelational(
"posts",
{ title: "Hello World", content: "My first post", views: 0 },
{ userId: "user_123" }
);
// Create comment
await db.createRelational(
"comments",
{ text: "Great post!", likes: 0 },
{ postId: post.id, userId: "user_456" }
);Document structure in Firestore:
{
"data": { "text": "Great post!", "likes": 0 },
"refs": { "postId": "p123", "userId": "u456" },
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}Query by References
// Get all comments for a post
const comments = await db.queryByRef("comments", "postId", "post_123");
// Query with data filters
const approvedComments = await db.queryByRefWithData(
"comments",
"postId",
"post_123",
{ approved: true }
);
// Query by multiple refs
const userPostComments = await db.queryByRefs("comments", {
postId: "post_123",
userId: "user_456",
});
// Get flattened results (merges data + refs)
const flatComments = await db.queryByRefFlattened("comments", "postId", "post_123");
// Returns: [{ id: "c1", text: "Great!", likes: 5, postId: "p123", userId: "u456" }]Update Relational Data
// Update only data (preserves refs)
await db.updateData("comments", "comment_1", { likes: 10 });
// Update only refs (preserves data)
await db.updateRefs("comments", "comment_1", { featured: "true" });
// Update both
await db.updateRelational(
"comments",
"comment_1",
{ likes: 15 }, // data updates
{ highlighted: "true" } // ref updates
);Relationship Operations
// Toggle relationship (like/unlike, follow/unfollow)
const result = await db.toggleRelation(
"likes",
{ value: 1 },
{ postId: "post_123", userId: "user_456" }
);
console.log(result.action); // "created" or "deleted"
// Find or create (prevents duplicates)
const { id, created } = await db.findOrCreateWithRefs(
"likes",
{ value: 1 },
{ postId: "post_123", userId: "user_456" }
);
// Check if relationship exists
const hasLiked = await db.relationExists("likes", {
postId: "post_123",
userId: "user_456",
});
// Count by reference
const commentCount = await db.countByRef("comments", "postId", "post_123");Advanced Relational
// Get related documents (join-like operation)
const { parent, children } = await db.getRelated(
"posts", // parent collection
"post_123", // parent id
"comments", // child collection
"postId" // relation key
);
// Cascade delete (deletes parent + all children)
await db.cascadeDeleteRelational("posts", "post_123", [
{ collection: "comments", refKey: "postId" },
{ collection: "likes", refKey: "postId" },
]);
// Aggregate counts by parent
const counts = await db.aggregateCountByParent(
"comments",
"postId",
["post_1", "post_2", "post_3"]
);
// Returns: { "post_1": 5, "post_2": 3, "post_3": 8 }4. Query Operations
Simple Queries
// Query with equality filters
const admins = await db.query("users", {
role: "admin",
active: true,
});
// Find one document
const admin = await db.findOne("users", { email: "admin@example.com" });
// Count with filters
const activeCount = await db.countWhere("users", { active: true });Advanced Queries
// Query with operators
const adults = await db.queryAdvanced("users", [
{ field: "age", op: ">=", value: 18 },
{ field: "verified", op: "==", value: true },
]);
// Query with ordering
const recentUsers = await db.queryOrdered(
"users",
{ active: true },
"createdAt",
"desc"
);
// Advanced query with ordering
const topScorers = await db.queryOrderedAdvanced(
"users",
[{ field: "score", op: ">=", value: 100 }],
"score",
"desc"
);Pagination
// First page
const page1 = await db.queryPaginated(
"users",
{ active: true },
10 // limit
);
// Next page
const page2 = await db.queryPaginated(
"users",
{ active: true },
10,
page1.lastDoc // start after last document
);
console.log(page1.hasMore); // true if more results exist
// Advanced pagination
const page = await db.queryPaginatedAdvanced(
"users",
[{ field: "age", op: ">=", value: 18 }],
20
);5. Batch Operations
// Batch create
await db.batchCreate("users", [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 35 },
]);
// Batch update
await db.batchUpdate("users", [
{ id: "user_1", data: { age: 26 } },
{ id: "user_2", data: { age: 31 } },
]);
// Batch delete
await db.batchDelete("users", ["user_1", "user_2", "user_3"]);
// Batch increment
await db.batchIncrement("posts", [
{ id: "post_1", field: "views", amount: 1 },
{ id: "post_2", field: "views", amount: 1 },
]);
// Delete entire collection
const result = await db.deleteCollection("temp_data");
console.log(result); // { deleted: 150, batches: 1 }6. Transactions
// Atomic increment
await db.atomicIncrement("users", "user_1", "balance", 100);
// Atomic decrement (with minimum value)
await db.atomicDecrement(
"users",
"user_1",
"balance",
50,
0 // minimum value (prevents negative balance)
);
// Atomic transfer between documents
await db.atomicTransfer(
"users", "user_1", // from
"users", "user_2", // to
"balance", // field
100 // amount
);
// Conditional update
const { updated } = await db.conditionalUpdate(
"posts",
"post_1",
(data) => data.status === "draft", // condition
{ status: "published" } // updates
);
// Read-modify-write pattern
await db.readModifyWrite(
"posts",
"post_1",
(data) => ({
views: data.views + 1,
lastViewed: new Date(),
})
);
// Compare and swap
const { swapped } = await db.compareAndSwap(
"settings",
"config_1",
"version",
1, // expected value
2 // new value
);
// Custom transaction
await db.runTransaction(async (tx) => {
const userRef = db.getFirestoreInstance().collection("users").doc("user_1");
const user = await tx.get(userRef);
const newBalance = user.data().balance + 100;
tx.update(userRef, { balance: newBalance });
});7. Migration Operations
// Convert single document to relational structure
await db.convertToRelational(
"users",
"user_1",
["groupId", "departmentId"] // keys to move to refs
);
// Batch convert entire collection
const result = await db.batchConvertToRelational(
"users",
["groupId", "departmentId"]
);
console.log(`Converted ${result.converted} documents`);
// Add field to all documents
await db.addFieldToAll("users", "verified", false);
// Remove field from all documents
await db.removeFieldFromAll("users", "deprecated_field");
// Rename field
await db.renameField("users", "old_name", "new_name");
// Custom transformation
await db.batchTransform("users", (doc) => ({
...doc,
fullName: `${doc.firstName} ${doc.lastName}`,
}));
// Copy collection
await db.copyCollection("users", "users_backup");
// Validate migration (dry run)
const validation = await db.validateMigration("users", (doc) => {
const errors = [];
if (!doc.email) errors.push("Missing email");
if (!doc.name) errors.push("Missing name");
return { valid: errors.length === 0, errors };
});๐ Multi-Database Examples
Environment-Based Setup
// db/config.ts
import { DatabaseManager } from "@zyljs/firestore-adapter";
export function initializeDatabases() {
const env = process.env.NODE_ENV || "development";
// Create all environment databases
DatabaseManager.createInstance("production", {
projectId: process.env.PROD_PROJECT_ID,
clientEmail: process.env.PROD_EMAIL,
privateKey: process.env.PROD_KEY,
});
DatabaseManager.createInstance("staging", {
projectId: process.env.STAGING_PROJECT_ID,
clientEmail: process.env.STAGING_EMAIL,
privateKey: process.env.STAGING_KEY,
});
DatabaseManager.createInstance("development", {
projectId: process.env.DEV_PROJECT_ID,
clientEmail: process.env.DEV_EMAIL,
privateKey: process.env.DEV_KEY,
});
// Set current environment as default
DatabaseManager.setDefault(env);
console.log(`โ
Databases initialized. Default: ${env}`);
}
// Use in your app
initializeDatabases();
export const db = DatabaseManager.getDefault();
export const prodDB = DatabaseManager.getInstance("production");
export const stagingDB = DatabaseManager.getInstance("staging");Multi-Tenant Setup
// services/tenant.service.ts
import { DatabaseManager } from "@zyljs/firestore-adapter";
export class TenantService {
connectTenant(tenantId: string, config: any) {
return DatabaseManager.createInstance(`tenant-${tenantId}`, config);
}
getTenantDB(tenantId: string) {
return DatabaseManager.getInstance(`tenant-${tenantId}`);
}
async disconnectTenant(tenantId: string) {
await DatabaseManager.closeInstance(`tenant-${tenantId}`);
}
}
// Usage
const tenantService = new TenantService();
// Connect tenant
tenantService.connectTenant("acme-corp", {
projectId: "acme-firestore",
clientEmail: process.env.ACME_EMAIL,
privateKey: process.env.ACME_KEY,
});
// Use tenant database
const acmeDB = tenantService.getTenantDB("acme-corp");
await acmeDB.create("users", { name: "John Doe" });Microservices Architecture
// Each service manages its own database
import { DatabaseManager } from "@zyljs/firestore-adapter";
// User service
const usersDB = DatabaseManager.createInstance("users", {
projectId: "users-microservice",
clientEmail: process.env.USERS_EMAIL,
privateKey: process.env.USERS_KEY,
});
// Orders service
const ordersDB = DatabaseManager.createInstance("orders", {
projectId: "orders-microservice",
clientEmail: process.env.ORDERS_EMAIL,
privateKey: process.env.ORDERS_KEY,
});
// Products service
const productsDB = DatabaseManager.createInstance("products", {
projectId: "products-microservice",
clientEmail: process.env.PRODUCTS_EMAIL,
privateKey: process.env.PRODUCTS_KEY,
});
// Use independently
await usersDB.create("users", { name: "Alice" });
await ordersDB.create("orders", { userId: "u1", total: 100 });
await productsDB.create("products", { name: "Widget", price: 29.99 });๐ง Configuration
Full Configuration Options
import { FireStoreDBAdapter } from "@zyljs/firestore-adapter";
const db = new FireStoreDBAdapter({
// Firebase credentials (required)
projectId: "my-project",
clientEmail: "service@my-project.iam.gserviceaccount.com",
privateKey: "-----BEGIN PRIVATE KEY-----\n...",
// Or use service account object
// serviceAccount: require('./serviceAccount.json'),
// Connection pooling (recommended for serverless)
enablePooling: true, // Default: true
idleTimeout: 300000, // 5 minutes
maxIdleTime: 600000, // 10 minutes
// Automatic retry (recommended)
enableRetry: true, // Default: true
retryConfig: {
maxRetries: 3,
initialDelay: 100,
maxDelay: 5000,
backoffMultiplier: 2,
},
// Monitoring
enableMetrics: true, // Default: true
enableTracing: true, // Default: true
// Graceful shutdown
enableGracefulShutdown: true, // Default: true
shutdownTimeout: 10000, // 10 seconds
// Logging
logLevel: "info", // "debug" | "info" | "warn" | "error"
logger: customLogger, // Optional custom logger
// Environment
environment: "production", // Default: process.env.NODE_ENV
});๐ Platform Support
Works on all platforms:
| Platform | Status | Notes |
|---|---|---|
| Vercel | โ Fully Supported | Serverless Functions |
| AWS Lambda | โ Fully Supported | With Serverless Framework |
| Google Cloud Functions | โ Fully Supported | Gen 1 & 2 |
| Google Cloud Run | โ Fully Supported | Containerized apps |
| Next.js | โ Fully Supported | API Routes & Server Actions |
| Express.js | โ Fully Supported | Traditional servers |
| Nest.js | โ Fully Supported | Enterprise Node.js |
| Cloudflare Workers | โ ๏ธ Limited | Use standard runtime only |
๐ API Reference
Complete Method List
CRUD (10 methods)
create()- Create with auto-generated IDset()- Create with specific IDread()- Read single documentupdate()- Update documentdelete()- Delete documentexists()- Check if existsupsert()- Create or updatelist()- List all documentscount()- Count documentsfindOne()- Find single document
Relational (20+ methods)
createRelational()- Create with refssetRelational()- Set with refsreadRelational()- Read relational formatreadFlattened()- Read merged formatupdateData()- Update data onlyupdateRefs()- Update refs onlyupdateRelational()- Update bothqueryByRef()- Query by single refqueryByRefWithData()- Query ref + filtersqueryByRefs()- Query multiple refsqueryByRefsWithData()- Query refs + filtersqueryByRefFlattened()- Query flat resultscountByRef()- Count by refrelationExists()- Check relationfindOneByRefs()- Find by refsfindOrCreateWithRefs()- Find or createupsertWithRefs()- Upsert with refstoggleRelation()- Toggle relationshipdeleteByRef()- Delete by refgetRelated()- Join-like operationcascadeDeleteRelational()- Cascade deletebatchCreateRelational()- Batch createaggregateCountByParent()- Aggregate counts
Query (10+ methods)
query()- Simple equality queriesqueryAdvanced()- Complex queriesqueryOrdered()- With orderingqueryOrderedAdvanced()- Ordered + complexqueryPaginated()- PaginationqueryPaginatedAdvanced()- Pagination + complexfindOneAdvanced()- Find with operatorscountWhere()- Count with filterscountWhereAdvanced()- Count advancedqueryWithOptions()- Full control
Batch (6 methods)
batchCreate()- Bulk createbatchSet()- Bulk setbatchUpdate()- Bulk updatebatchDelete()- Bulk deletebatchIncrement()- Bulk incrementdeleteCollection()- Delete collection
Transaction (7 methods)
runTransaction()- Custom transactionatomicIncrement()- Atomic addatomicDecrement()- Atomic subtractatomicTransfer()- Transfer between docsconditionalUpdate()- Update if conditionreadModifyWrite()- Read-modify-writecompareAndSwap()- CAS operation
Migration (8 methods)
convertToRelational()- Convert singlebatchConvertToRelational()- Convert allbatchTransform()- Custom transformaddFieldToAll()- Add fieldremoveFieldFromAll()- Remove fieldrenameField()- Rename fieldcopyCollection()- Copy collectionvalidateMigration()- Dry run
Utilities
getMetrics()- Get connection metricsgetFirestoreInstance()- Get raw Firestoreclose()- Close connectionserverTimestamp()- Get server timestamparrayUnion()- Array union operationarrayRemove()- Array remove operationincrement()- Increment field valuedeleteField()- Delete field
DatabaseManager (Static Class)
createInstance()- Create new instancegetInstance()- Get instance by namegetDefault()- Get default instancesetDefault()- Set default instancehasInstance()- Check if existsgetInstanceNames()- List all namesgetInstanceCount()- Count instancescloseInstance()- Close onecloseAll()- Close allgetAllMetrics()- All metricsgetInstanceMetrics()- Instance metricshealthCheck()- Health statusreset()- Reset all (testing)
๐ฅ Health Monitoring
import { DatabaseManager } from "@zyljs/firestore-adapter";
// Health check endpoint
app.get("/health", (req, res) => {
const health = DatabaseManager.healthCheck();
res.json(health);
});
// Metrics endpoint
app.get("/metrics", (req, res) => {
const metrics = DatabaseManager.getAllMetrics();
res.json(metrics);
});
// Database info
app.get("/db/info", (req, res) => {
res.json({
instances: DatabaseManager.getInstanceNames(),
count: DatabaseManager.getInstanceCount(),
});
});๐งช Testing
import { DatabaseManager } from "@zyljs/firestore-adapter";
describe("User Service", () => {
beforeAll(() => {
DatabaseManager.createInstance("test", {
projectId: "test-project",
clientEmail: process.env.TEST_EMAIL,
privateKey: process.env.TEST_KEY,
});
});
afterAll(async () => {
await DatabaseManager.reset();
});
it("should create user", async () => {
const db = DatabaseManager.getInstance("test");
const user = await db.create("users", { name: "Test" });
expect(user.id).toBeDefined();
});
});๐ Security Best Practices
- Never commit credentials - Use environment variables
- Use minimal permissions - Grant only necessary Firestore roles
- Implement Firestore security rules - Don't rely solely on server-side auth
- Validate input data - Always validate before database operations
- Use separate projects - Different projects for dev/staging/production
- Enable audit logging - Track database access in production
- Rotate service account keys - Regularly update credentials
๐ Environment Variables
# Main database
FIREBASE_PROJECT_ID=your-project-id
FIREBASE_CLIENT_EMAIL=service@your-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
# Additional databases (optional)
FIREBASE_ANALYTICS_PROJECT_ID=analytics-project
FIREBASE_ANALYTICS_CLIENT_EMAIL=service@analytics.iam.gserviceaccount.com
FIREBASE_ANALYTICS_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n..."
# Application
NODE_ENV=production๐ค Contributing
Contributions welcome! Please read CONTRIBUTING.md first.
๐ License
MIT ยฉ [Your Name]
๐ Links
๐ฌ Support
- ๐ง Email: abdulahadsn5680.dev@gmail.com
Built with โค๏ธ for the serverless community