@umituz/web-firebase
v3.6.24
Published
Comprehensive Firebase integration with domain-based architecture for web applications
Maintainers
Readme
@umituz/web-firebase
Comprehensive Firebase integration with Domain-Driven Design (DDD) architecture for web applications.
🚀 Features
Core Features
- ✅ DDD Architecture - Clean separation of concerns with 4 distinct layers
- ✅ Type-Safe - Full TypeScript support with strict typing
- ✅ Repository Pattern - Abstract data access from business logic
- ✅ Error Handling - Domain-specific error types
- ✅ Zero Code Duplication - Package-driven development ready
Firestore Services (NEW v3.6.0)
- ✅ QueryBuilder - Fluent API for complex queries
- ✅ TransactionManager - Atomic multi-document operations
- ✅ BatchOperationManager - Optimized batch operations with auto-chunking
- ✅ RealTimeSubscriptionManager - Real-time updates with auto-cleanup
- ✅ BaseFirestoreEntity - Audit fields and versioning
- ✅ FirestoreError Domain - Specific error types for better debugging
Auth Features
- ✅ Google & Apple OAuth - Built-in support for Google Sign-In and Apple Sign-In
- ✅ Configurable Auth - Enable/disable providers, configure scopes and custom parameters
- ✅ Auto User Document Creation - Automatic Firestore user document creation with custom settings
- ✅ Provider Linking/Unlinking - Link multiple auth providers to a single account
Firebase Exports
- ✅ All Firestore Functions - Exported from single package for convenience
- ✅ Query Functions - where, orderBy, limit, etc.
- ✅ Timestamp Functions - serverTimestamp
- ✅ Transaction Functions - runTransaction, writeBatch
📦 Installation
npm install @umituz/web-firebase firebase@^12🏗️ Architecture
This package follows Domain-Driven Design principles with clear layer separation:
┌─────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ React Hooks, Providers - UI interactions │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────┴────────────────────────────────────┐
│ Application Layer │
│ Use Cases, DTOs - Business logic orchestration │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────┴────────────────────────────────────┐
│ Domain Layer │
│ Entities, Interfaces, Errors - Core business model │
└────────────────────┬────────────────────────────────────┘
│
┌────────────────────┴────────────────────────────────────┐
│ Infrastructure Layer │
│ Firebase Adapters - External service implementations │
└─────────────────────────────────────────────────────────┘🎯 Quick Start
1. Initialize Firebase
// Initialize Firebase in your app
import { initializeFirebaseApp } from '@umituz/web-firebase/infrastructure'
const firebaseConfig = {
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
}
initializeFirebaseApp(firebaseConfig)2. Configure Authentication
import { initAuthConfig } from '@umituz/web-firebase/config'
// Initialize auth configuration
initAuthConfig({
// Enable/disable email/password authentication
emailPasswordEnabled: true,
// Enable/disable OAuth providers
googleEnabled: true,
appleEnabled: true,
// Configure Google OAuth
googleScopes: ['profile', 'email'],
googleCustomParameters: {
prompt: 'select_account',
},
// Require email verification for new users
requireEmailVerification: true,
// Automatically create user document in Firestore
autoCreateUserDocument: true,
// Default user settings
defaultUserSettings: {
theme: 'system',
language: 'tr',
timezone: 'Europe/Istanbul',
currency: 'TRY',
notifications: {
email: true,
push: true,
marketing: false,
security: true,
weeklyDigest: false,
},
privacy: {
profileVisibility: 'public',
showEmail: false,
showPhone: false,
dataSharing: false,
},
},
// Default subscription plan
defaultSubscriptionPlan: 'free',
// Session persistence
persistence: 'local',
})2. Use Domain Layer (Types & Interfaces)
import type { User, UserProfile } from '@umituz/web-firebase/domain'
import type { IAuthRepository, IUserRepository } from '@umituz/web-firebase/domain'
// Use domain entities in your app
const user: User = {
profile: {
email: '[email protected]',
displayName: 'John Doe',
createdAt: Date.now(),
},
settings: {
theme: 'light',
language: 'en',
},
// ... other fields
}3. Use Application Layer (Use Cases)
import { SignInUseCase, SignUpUseCase } from '@umituz/web-firebase/application'
import { AuthAdapter } from '@umituz/web-firebase/infrastructure'
// Create adapters
const authRepository = new AuthAdapter()
const userRepository = new FirestoreAdapter()
// Create use cases
const signInUseCase = new SignInUseCase(authRepository)
const signUpUseCase = new SignUpUseCase(authRepository)
// Execute use cases
const result = await signInUseCase.execute({
email: '[email protected]',
password: 'Password123',
})4. Use Infrastructure Layer (Adapters)
import { AuthAdapter, FirestoreAdapter, StorageAdapter } from '@umituz/web-firebase/infrastructure'
// Direct adapter usage
const authAdapter = new AuthAdapter()
await authAdapter.signIn('[email protected]', 'Password123')
const firestoreAdapter = new FirestoreAdapter()
const user = await firestoreAdapter.getUser('user-id')
const storageAdapter = new StorageAdapter()
const result = await storageAdapter.uploadFile('user-id', 'path', file)5. Use Presentation Layer (React Hooks)
import { FirebaseProvider } from '@umituz/web-firebase/presentation'
import { useAuth } from '@umituz/web-firebase/presentation'
function App() {
return (
<FirebaseProvider config={firebaseConfig}>
<YourApp />
</FirebaseProvider>
)
}
function Profile() {
const { user, loading, signIn, signOut } = useAuth({
authRepository: new AuthAdapter(),
userRepository: new FirestoreAdapter(),
})
if (loading) return <div>Loading...</div>
return (
<div>
<p>Welcome, {user?.profile.displayName}</p>
<button onClick={() => signOut()}>Sign Out</button>
</div>
)
}📚 Subpath Imports
⚠️ IMPORTANT: Do NOT use the root barrel import. Use subpath imports for better tree-shaking and clear dependencies.
Domain Layer
// Entities
import type { User, UserProfile, UserSettings } from '@umituz/web-firebase/domain'
import type { FileMetadata, UploadProgress, UploadResult } from '@umituz/web-firebase/domain'
// Repository Interfaces
import type { IAuthRepository } from '@umituz/web-firebase/domain'
import type { IUserRepository } from '@umituz/web-firebase/domain'
import type { IFileRepository } from '@umituz/web-firebase/domain'
// Errors
import { AuthError, AuthErrorCode } from '@umituz/web-firebase/domain'
import { RepositoryError, RepositoryErrorCode } from '@umituz/web-firebase/domain'Application Layer
// Use Cases
import { SignInUseCase, SignUpUseCase } from '@umituz/web-firebase/application'
import { UpdateProfileUseCase, DeleteAccountUseCase } from '@umituz/web-firebase/application'
// DTOs
import type { SignInDTO, SignUpDTO } from '@umituz/web-firebase/application'Infrastructure Layer
// Firebase Client
import { initializeFirebaseApp, getFirebaseAuth, getFirebaseDB } from '@umituz/web-firebase/infrastructure'
// Adapters
import { AuthAdapter } from '@umituz/web-firebase/infrastructure'
import { FirestoreAdapter } from '@umituz/web-firebase/infrastructure'
import { StorageAdapter } from '@umituz/web-firebase/infrastructure'
// Utils
import { generateUniqueFilename, getFileExtension } from '@umituz/web-firebase/infrastructure'Presentation Layer
// Providers
import { FirebaseProvider } from '@umituz/web-firebase/presentation'
// Hooks
import { useAuth } from '@umituz/web-firebase/presentation'
import { useUser } from '@umituz/web-firebase/presentation'
import { useFirestore } from '@umituz/web-firebase/presentation'
import { useStorage } from '@umituz/web-firebase/presentation'🔥 Detailed Usage
Authentication
import { AuthAdapter } from '@umituz/web-firebase/infrastructure'
const auth = new AuthAdapter()
// Sign in with email/password
const credential = await auth.signIn('[email protected]', 'password')
// Sign up
const credential = await auth.signUp('[email protected]', 'password', 'John Doe')
// Sign in with Google
const credential = await auth.signInWithGoogle()
// Sign in with Apple
const credential = await auth.signInWithApple()
// Sign in with redirect flow (for mobile/better UX)
const credential = await auth.signInWithGoogle(true) // useRedirect = true
// Link Google to current user
await auth.linkGoogle()
// Link Apple to current user
await auth.linkApple()
// Unlink a provider
await auth.unlinkProvider('google.com')
// Get ID token (for API calls)
const token = await auth.getIdToken()
const tokenForceRefresh = await auth.getIdToken(true)
// Refresh ID token
await auth.refreshToken()
// Sign out
await auth.signOut()
// Password reset
await auth.sendPasswordReset('[email protected]')
// Update profile
await auth.updateProfile({ displayName: 'Jane Doe' })
// Update email
await auth.updateEmail('[email protected]', 'current-password')
// Update password
await auth.updatePassword('current-password', 'new-password')
// Delete account
await auth.deleteAccount('current-password')User Management (Firestore)
import { FirestoreAdapter } from '@umituz/web-firebase/infrastructure'
const firestore = new FirestoreAdapter()
// Get user
const user = await firestore.getUser('user-id')
// Get user by email
const user = await firestore.getUserByEmail('[email protected]')
// Create user
await firestore.createUser('user-id', {
profile: {
email: '[email protected]',
displayName: 'John Doe',
createdAt: Date.now(),
},
})
// Update user
await firestore.updateUser('user-id', {
'settings.theme': 'dark',
})
// Update profile
await firestore.updateProfile('user-id', {
displayName: 'Jane Doe',
photoURL: 'https://...',
})
// Update settings
await firestore.updateSettings('user-id', {
theme: 'dark',
language: 'tr',
})
// Update subscription
await firestore.updateSubscription('user-id', {
plan: 'premium',
status: 'active',
})
// Update last login
await firestore.updateLastLogin('user-id')
// Delete user
await firestore.deleteUser('user-id')
// Query users
const users = await firestore.queryUsers([
where('profile.email', '==', '[email protected]'),
])
// Subscribe to user updates
const unsubscribe = firestore.subscribeToUser(
'user-id',
(user) => console.log('User updated:', user),
(error) => console.error('Error:', error)
)File Storage
import { StorageAdapter } from '@umituz/web-firebase/infrastructure'
const storage = new StorageAdapter()
// Upload file
const result = await storage.uploadFile('user-id', 'path/to/file', file, {
onProgress: (progress) => console.log(`${progress.progress}%`),
})
// Upload specific types
await storage.uploadImage('user-id', file, 'custom-name.jpg')
await storage.uploadVideo('user-id', file, 'custom-name.mp4')
await storage.uploadDocument('user-id', file, 'custom-name.pdf')
await storage.uploadProfilePicture('user-id', file)
// Get download URL
const url = await storage.getDownloadURL('users/user-id/path/to/file')
// Delete file
await storage.deleteFile('users/user-id/path/to/file')
await storage.deleteImage('user-id', 'filename.jpg')
await storage.deleteVideo('user-id', 'filename.mp4')
await storage.deleteProfilePicture('user-id', 'filename.jpg')
await storage.deleteUserFiles('user-id')
// List files
const urls = await storage.listUserFiles('user-id')
const images = await storage.listUserImages('user-id')
const videos = await storage.listUserVideos('user-id')
// Get metadata
const metadata = await storage.getFileMetadata('users/user-id/file.jpg')
// Query files
const { files, totalCount, hasMore } = await storage.queryFiles('user-id')
// Get storage stats
const stats = await storage.getStorageStats('user-id')
// Validate file
const isValid = storage.validateFile(file, {
maxSizeMB: 10,
allowedTypes: ['image/jpeg', 'image/png'],
})
// Check file types
storage.isImageFile(file)
storage.isVideoFile(file)
storage.isDocumentFile(file)
// Generate unique filename
const filename = storage.generateUniqueFilename('photo.jpg')
// Get file extension
const ext = storage.getFileExtension('photo.jpg')React Hooks
import {
useAuth,
useUser,
useFirestore,
useStorage,
} from '@umituz/web-firebase/presentation'
function MyComponent() {
// Auth hook with Google & Apple OAuth
const {
firebaseUser,
user: userData,
loading,
error,
// Email/Password
signIn,
signUp,
// OAuth Providers
signInWithGoogle,
signInWithApple,
// Provider management
linkGoogle,
linkApple,
unlinkProvider,
// Account management
signOut,
sendPasswordReset,
resendEmailVerification,
updateProfile,
updateEmail,
updatePassword,
deleteAccount,
// Token management
getIdToken,
refreshToken,
// User data
refreshUser,
// Config
googleEnabled,
appleEnabled,
emailPasswordEnabled,
} = useAuth()
// Example: Sign in with Google
const handleGoogleSignIn = async () => {
try {
const user = await signInWithGoogle()
console.log('Signed in with Google:', user)
} catch (error) {
console.error('Sign in failed:', error)
}
}
// Example: Sign in with Apple
const handleAppleSignIn = async () => {
try {
const user = await signInWithApple()
console.log('Signed in with Apple:', user)
} catch (error) {
console.error('Sign in failed:', error)
}
}
// Example: Get ID token for API call
const makeApiCall = async () => {
const token = await getIdToken()
const response = await fetch('/api/protected', {
headers: {
Authorization: `Bearer ${token}`,
},
})
return response.json()
}
return (
<div>
{loading ? (
<p>Loading...</p>
) : firebaseUser ? (
<>
<p>Welcome, {userData?.profile.displayName}</p>
<button onClick={handleGoogleSignIn}>Re-authenticate with Google</button>
<button onClick={() => linkApple()}>Link Apple Account</button>
<button onClick={() => signOut()}>Sign Out</button>
</>
) : (
<>
{emailPasswordEnabled && (
<button onClick={() => signIn('[email protected]', 'password')}>
Sign In with Email
</button>
)}
{googleEnabled && (
<button onClick={handleGoogleSignIn}>Sign In with Google</button>
)}
{appleEnabled && (
<button onClick={handleAppleSignIn}>Sign In with Apple</button>
)}
</>
)}
</div>
)
}🔄 Migration from v1.x to v2.0.0
Version 2.0.0 is a breaking change with complete DDD architecture. Here's how to migrate:
Before (v1.x)
import { FirebaseService, AuthService } from '@umituz/web-firebase'
const service = new FirebaseService(config)
await service.auth.signIn(email, password)After (v2.0.0)
import { initializeFirebaseApp } from '@umituz/web-firebase/infrastructure'
import { AuthAdapter } from '@umituz/web-firebase/infrastructure'
initializeFirebaseApp(config)
const auth = new AuthAdapter()
await auth.signIn(email, password)Key Changes
- Subpath imports required - No more root barrel imports
- Repository pattern - Use adapters instead of services
- Error types - Domain-specific errors instead of generic errors
- Use cases - Business logic in application layer
- Types - Import domain types separately
Migration Steps
Update package version:
npm install @umituz/web-firebase@^2.0.0Update imports:
// Before import { AuthService } from '@umituz/web-firebase' // After import { AuthAdapter } from '@umituz/web-firebase/infrastructure'Update initialization:
// Before const firebase = new FirebaseService(config) // After initializeFirebaseApp(config)Update method calls:
// Before - service pattern const user = await firebase.auth.getUser(uid) // After - repository pattern const user = await firestoreAdapter.getUser(uid)
📦 Package Structure
src/
├── domain/ # Core business model
│ ├── entities/ # Domain entities
│ │ ├── user.entity.ts
│ │ └── file.entity.ts
│ ├── interfaces/ # Repository interfaces
│ │ ├── auth.repository.interface.ts
│ │ ├── user.repository.interface.ts
│ │ └── file.repository.interface.ts
│ └── errors/ # Domain errors
│ ├── auth.errors.ts
│ └── repository.errors.ts
│
├── application/ # Business logic
│ ├── use-cases/ # Use cases
│ │ ├── auth/
│ │ │ ├── sign-in.use-case.ts
│ │ │ ├── sign-up.use-case.ts
│ │ │ └── reset-password.use-case.ts
│ │ └── user/
│ │ ├── update-profile.use-case.ts
│ │ └── delete-account.use-case.ts
│ └── dto/ # Data Transfer Objects
│ ├── auth.dto.ts
│ └── user.dto.ts
│
├── infrastructure/ # External integrations
│ ├── firebase/ # Firebase adapters
│ │ ├── client.ts
│ │ ├── auth.adapter.ts
│ │ ├── firestore.adapter.ts
│ │ └── storage.adapter.ts
│ └── utils/ # Utility functions
│ └── storage.util.ts
│
└── presentation/ # UI layer
├── hooks/ # React hooks
│ ├── useAuth.ts
│ ├── useUser.ts
│ ├── useFirestore.ts
│ └── useStorage.ts
└── providers/ # Context providers
└── FirebaseProvider.tsx🎯 Use Cases
- Social Media Apps - User profiles, posts, media storage
- E-commerce - Product catalogs, user accounts, order management
- SaaS - Subscription management, user settings, analytics
- Chat Apps - Real-time messaging, user presence
- Blogs/CMS - Content management, media uploads
🔥 Firestore Services (v3.6.0)
QueryBuilder - Fluent Query API
import { createQueryBuilder } from '@umituz/web-firebase/firestore'
// Build complex queries with clean syntax
const query = createQueryBuilder()
.equals('userId', userId)
.equals('isActive', true)
.greaterThanOrEqualTo('createdAt', startDate.toISOString())
.descending('updatedAt')
.limitTo(20)
.build()
const users = await userRepository.getAll(query)TransactionManager - Atomic Operations
import { transactionManager } from '@umituz/web-firebase/firestore'
// Atomic read-modify-write operation
await transactionManager.atomicUpdate('agencies', agencyId, (current) => {
if (!current || current.ownerId !== fromUserId) {
throw new Error('Not authorized')
}
return {
...current,
ownerId: toUserId,
transferredAt: new Date().toISOString()
}
})BatchOperationManager - Bulk Operations
import { batchOperationManager } from '@umituz/web-firebase/firestore'
// Delete multiple documents efficiently
await batchOperationManager.deleteMultiple('agencies', agencyIds)
// Update multiple documents
const updates = agencyIds.map(id => ({
type: 'update' as const,
collection: 'agencies',
documentId: id,
data: { status: 'archived' }
}))
await batchOperationManager.executeBatch(updates, {
maxOperations: 500,
batchDelay: 100
})RealTimeSubscriptionManager - Live Updates
import { realTimeSubscriptionManager } from '@umituz/web-firebase/firestore'
// Subscribe to document changes
const unsubscribe = realTimeSubscriptionManager.subscribeToDocument(
'users',
userId,
(user) => console.log('User updated:', user),
(error) => console.error('Error:', error)
)
// Auto-cleanup on unmount
return () => unsubscribe()Enhanced FirestoreRepository
import { FirestoreRepository } from '@umituz/web-firebase/repository'
class UserRepository extends FirestoreRepository<User> {
constructor() {
super('users')
}
// Pagination with cursor
async getPaginated(pageSize: number, startAfter?: User) {
return this.paginate([], pageSize, startAfter)
}
// Real-time updates
subscribeToUser(userId: string, callback: (user: User | null) => void) {
return this.watchById(userId, callback)
}
// Upsert (create or update)
async syncUser(userId: string, data: Omit<User, 'id'>) {
return this.upsert(userId, data)
}
// Count documents
async countActiveUsers() {
const query = createQueryBuilder()
.equals('isActive', true)
.build()
return this.count(query)
}
}📦 All Firebase Exports
All commonly used Firestore functions are exported from @umituz/web-firebase:
import {
// Query functions
collection, doc, query,
where, orderBy, limit,
startAfter, startAt, endAt, endBefore,
// CRUD operations
getDoc, getDocs, setDoc, updateDoc, deleteDoc, addDoc,
// Real-time
onSnapshot,
// Transactions & Batches
runTransaction, writeBatch,
// Timestamp
serverTimestamp,
// Types
type Firestore, QueryConstraint, DocumentData,
type CollectionReference, DocumentReference, Query
} from '@umituz/web-firebase'📝 License
MIT
🤝 Contributing
Contributions are welcome! Please follow our DDD architecture principles when contributing.
Made with ❤️ by umituz
