Mutations
Mutations Service
Comprehensive CRUD operations for Firestore with validation, batch processing, and reactive state management
Mutations Service
The firekitDocMutations
service provides comprehensive CRUD operations for Firestore documents with validation, batch processing, error handling, and reactive state management using Svelte 5 runes.
Overview
The mutations service provides:
- Complete CRUD operations (Create, Read, Update, Delete)
- Batch operations for multiple documents
- Data validation and sanitization
- Automatic timestamp management
- Error handling and retry mechanisms
- Performance analytics and monitoring
- Reactive state management
- Type-safe operations
Quick Start
<script>
import { firekitDocMutations } from 'svelte-firekit';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
let title = '';
let content = '';
let loading = false;
async function createPost() {
loading = true;
try {
const result = await firekitDocMutations.add(
'posts',
{
title,
content,
authorId: 'user123',
published: false
},
{
timestamps: true,
validate: true
}
);
console.log('Post created:', result.id);
title = '';
content = '';
} catch (error) {
console.error('Failed to create post:', error);
} finally {
loading = false;
}
}
</script>
<form onsubmit|preventDefault={createPost}>
<Input bindvalue={title} placeholder="Post title" required />
<Input bindvalue={content} placeholder="Post content" required />
<Button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Post'}
</Button>
</form>
Basic CRUD Operations
Create Documents
import { firekitDocMutations } from 'svelte-firekit';
// Basic document creation
const result = await firekitDocMutations.add('users', {
name: 'John Doe',
email: '[email protected]',
age: 30
});
console.log('Document created with ID:', result.id);
// Create with custom ID
const customResult = await firekitDocMutations.set('users/john123', {
name: 'John Doe',
email: '[email protected]'
});
// Create with merge option
const mergeResult = await firekitDocMutations.set(
'users/john123',
{
age: 31,
lastUpdated: new Date()
},
{ merge: true }
);
Read Documents
import { firekitDocMutations } from 'svelte-firekit';
// Get single document
const result = await firekitDocMutations.getDoc('users/123');
if (result.success) {
console.log('User data:', result.data);
}
// Check if document exists
const existsResult = await firekitDocMutations.exists('users/123');
if (existsResult.exists) {
console.log('Document exists');
}
// Note: For multiple documents, use firekitCollection with where clauses
Update Documents
import { firekitDocMutations } from 'svelte-firekit';
// Update entire document
await firekitDocMutations.set('users/123', {
name: 'John Doe',
email: '[email protected]',
age: 30
});
// Update specific fields
await firekitDocMutations.update('users/123', {
age: 31,
lastUpdated: new Date()
});
// Update with validation
await firekitDocMutations.update(
'users/123',
{
email: '[email protected]'
},
{
validate: true,
validateFields: ['email']
}
);
Delete Documents
import { firekitDocMutations } from 'svelte-firekit';
// Delete single document
await firekitDocMutations.delete('users/123');
// Delete document
const result = await firekitDocMutations.delete('users/123');
if (result.success) {
console.log('User deleted successfully');
}
Advanced Operations
Batch Operations
import { firekitDocMutations } from 'svelte-firekit';
// Batch operations
const batchResult = await firekitDocMutations.batch([
{ type: 'create', path: 'users', data: { name: 'Alice', email: '[email protected]' } },
{ type: 'update', path: 'users/123', data: { age: 31 } },
{ type: 'delete', path: 'users/456' }
]);
if (batchResult.success) {
console.log('Batch operations completed');
} else {
console.error('Batch failed:', batchResult.error);
}
// Batch with configuration
const validatedBatchResult = await firekitDocMutations.batch(
[
{
type: 'create',
path: 'posts',
data: { title: 'New Post', content: 'Post content', authorId: 'user123' }
}
],
{
validate: true,
timestamps: true
}
);
Transaction Operations
import { firekitDocMutations } from 'svelte-firekit';
// Note: Transactions are not directly supported in this version
// Use batch operations for multiple related changes
const batchResult = await firekitDocMutations.batch([
{ type: 'update', path: 'users/123', data: { balance: newBalance } },
{ type: 'create', path: 'transactions', data: {
userId: '123',
amount: 100,
type: 'credit',
timestamp: new Date()
});
return { newBalance };
});
console.log('Transaction completed, new balance:', result.newBalance);
Array Operations
import { firekitDocMutations } from 'svelte-firekit';
// Array union (add unique elements)
await firekitDocMutations.arrayUnion('users/123', 'tags', ['javascript', 'svelte']);
// Array remove (remove elements)
await firekitDocMutations.arrayRemove('users/123', 'tags', ['old-tag']);
// Increment numeric values
await firekitDocMutations.increment('posts/123', 'views', 1);
await firekitDocMutations.increment('posts/123', 'likes', 5);
Data Validation
Schema Validation
import { firekitDocMutations } from 'svelte-firekit';
// Define validation schema
const userSchema = {
name: { type: 'string', required: true, minLength: 2 },
email: { type: 'string', required: true, pattern: /^[^s@]+@[^s@]+.[^s@]+$/ },
age: { type: 'number', min: 0, max: 150 },
verified: { type: 'boolean', default: false }
};
// Create with validation
const result = await firekitDocMutations.add(
'users',
{
name: 'John Doe',
email: '[email protected]',
age: 30
},
{
validate: true,
schema: userSchema
}
);
Custom Validation
import { firekitDocMutations } from 'svelte-firekit';
// Custom validation function
const validateUser = (data: any) => {
const errors = [];
if (!data.name || data.name.length < 2) {
errors.push('Name must be at least 2 characters');
}
if (!data.email || !data.email.includes('@')) {
errors.push('Valid email is required');
}
if (data.age && (data.age < 0 || data.age > 150)) {
errors.push('Age must be between 0 and 150');
}
if (errors.length > 0) {
throw new Error(`Validation failed: ${errors.join(', ')}`);
}
return data;
};
// Use custom validation
await firekitDocMutations.add('users', userData, {
validate: true,
validator: validateUser
});
Field-level Validation
import { firekitDocMutations } from 'svelte-firekit';
// Validate specific fields
await firekitDocMutations.update(
'users/123',
{
email: '[email protected]',
age: 31
},
{
validate: true,
validateFields: ['email', 'age'],
schema: {
email: { type: 'string', pattern: /^[^s@]+@[^s@]+.[^s@]+$/ },
age: { type: 'number', min: 0, max: 150 }
}
}
);
Timestamp Management
Automatic Timestamps
import { firekitDocMutations } from 'svelte-firekit';
// Create with automatic timestamps
const result = await firekitDocMutations.add(
'posts',
{
title: 'My Post',
content: 'Post content'
},
{
timestamps: true // Adds createdAt and updatedAt
}
);
// Update with timestamp
await firekitDocMutations.update(
'posts/123',
{
content: 'Updated content'
},
{
timestamps: true // Updates updatedAt
}
);
Custom Timestamp Fields
import { firekitDocMutations } from 'svelte-firekit';
// Custom timestamp field names
await firekitDocMutations.add(
'posts',
{
title: 'My Post',
content: 'Post content'
},
{
timestamps: {
createdAt: 'created',
updatedAt: 'modified'
}
}
);
Server Timestamps
import { firekitDocMutations } from 'svelte-firekit';
// Use server timestamps
await firekitDocMutations.add('posts', {
title: 'My Post',
content: 'Post content',
createdAt: firekitDocMutations.serverTimestamp(),
updatedAt: firekitDocMutations.serverTimestamp()
});
Error Handling
Basic Error Handling
import { firekitDocMutations } from 'svelte-firekit';
try {
const result = await firekitDocMutations.add('users', userData);
console.log('User created:', result.id);
} catch (error) {
if (error.code === 'permission-denied') {
console.error('Permission denied');
} else if (error.code === 'not-found') {
console.error('Document not found');
} else {
console.error('Operation failed:', error.message);
}
}
Retry Configuration
import { firekitDocMutations } from 'svelte-firekit';
// Operation with retry
const result = await firekitDocMutations.add('users', userData, {
retry: {
enabled: true,
maxAttempts: 3,
delay: 1000,
backoff: 'exponential'
}
});
Error Recovery
import { firekitDocMutations } from 'svelte-firekit';
async function createUserWithRetry(userData: any) {
let attempts = 0;
const maxAttempts = 3;
while (attempts < maxAttempts) {
try {
return await firekitDocMutations.add('users', userData);
} catch (error) {
attempts++;
if (error.code === 'unavailable' && attempts < maxAttempts) {
// Wait before retry
await new Promise((resolve) => setTimeout(resolve, 1000 * attempts));
continue;
}
throw error;
}
}
}
Performance Analytics
Operation Monitoring
import { firekitDocMutations } from 'svelte-firekit';
// Monitor operation performance
const result = await firekitDocMutations.add('users', userData, {
analytics: {
enabled: true,
operationName: 'create_user',
trackPerformance: true
}
});
// Access performance data
console.log('Operation duration:', result.performance?.duration);
console.log('Bytes written:', result.performance?.bytesWritten);
Batch Performance
import { firekitDocMutations } from 'svelte-firekit';
const batch = firekitDocMutations.batch({
analytics: {
enabled: true,
operationName: 'batch_user_updates'
}
});
// Add operations
for (let i = 0; i < 100; i++) {
batch.update(`users/${i}`, { lastSeen: new Date() });
}
const result = await batch.commit();
console.log('Batch performance:', result.performance);
Svelte Component Integration
Form Component with Mutations
<script>
import { firekitDocMutations } from 'svelte-firekit';
import { Button } from '$lib/components/ui/button';
import { Input } from '$lib/components/ui/input';
export let collection: string;
export let documentId?: string;
let formData = {
name: '',
email: '',
age: ''
};
let loading = false;
let error = '';
// Load existing data if editing
$effect(async () => {
if (documentId) {
try {
const doc = await firekitDocMutations.get(`${collection}/${documentId}`);
if (doc.exists()) {
formData = { ...doc.data() };
}
} catch (err) {
error = 'Failed to load document';
}
}
});
async function save() {
loading = true;
error = '';
try {
if (documentId) {
// Update existing document
await firekitDocMutations.update(`${collection}/${documentId}`, formData, {
timestamps: true,
validate: true
});
} else {
// Create new document
const result = await firekitDocMutations.add(collection, formData, {
timestamps: true,
validate: true
});
documentId = result.id;
}
} catch (err) {
error = err.message;
} finally {
loading = false;
}
}
</script>
<form onsubmit|preventDefault={save} class="mutation-form">
<div class="form-field">
<label for="name">Name</label>
<Input id="name" bindvalue={formData.name} required />
</div>
<div class="form-field">
<label for="email">Email</label>
<Input id="email" type="email" bindvalue={formData.email} required />
</div>
<div class="form-field">
<label for="age">Age</label>
<Input id="age" type="number" bindvalue={formData.age} />
</div>
{#if error}
<div class="error">{error}</div>
{/if}
<Button type="submit" disabled={loading}>
{loading ? 'Saving...' : documentId ? 'Update' : 'Create'}
</Button>
</form>
<style>
.mutation-form {
display: flex;
flex-direction: column;
gap: 1rem;
max-width: 400px;
}
.form-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.error {
color: #dc2626;
font-size: 0.875rem;
}
</style>
Batch Operations Component
<script>
import { firekitDocMutations } from 'svelte-firekit';
import { Button } from '$lib/components/ui/button';
let selectedUsers: string[] = [];
let loading = false;
let progress = 0;
async function deleteSelected() {
if (selectedUsers.length === 0) return;
loading = true;
progress = 0;
try {
const batch = firekitDocMutations.batch({
analytics: { enabled: true, operationName: 'batch_delete_users' }
});
selectedUsers.forEach((userId) => {
batch.delete(`users/${userId}`);
});
await batch.commit();
selectedUsers = [];
progress = 100;
} catch (error) {
console.error('Batch delete failed:', error);
} finally {
loading = false;
}
}
async function updateSelected(field: string, value: any) {
if (selectedUsers.length === 0) return;
loading = true;
progress = 0;
try {
const batch = firekitDocMutations.batch({
timestamps: true
});
selectedUsers.forEach((userId) => {
batch.update(`users/${userId}`, { [field]: value });
});
await batch.commit();
progress = 100;
} catch (error) {
console.error('Batch update failed:', error);
} finally {
loading = false;
}
}
</script>
<div class="batch-operations">
<h3>Batch Operations</h3>
{#if selectedUsers.length > 0}
<p>Selected: {selectedUsers.length} users</p>
<div class="batch-actions">
<Button onclick={() => updateSelected('active', true)} disabled={loading}>
Activate Selected
</Button>
<Button onclick={() => updateSelected('active', false)} disabled={loading}>
Deactivate Selected
</Button>
<Button onclick={deleteSelected} disabled={loading} variant="destructive">
Delete Selected
</Button>
</div>
{#if loading}
<div class="progress">
<div class="progress-bar" style="width: {progress}%"></div>
</div>
{/if}
{:else}
<p>No users selected</p>
{/if}
</div>
<style>
.batch-operations {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
}
.batch-actions {
display: flex;
gap: 0.5rem;
margin: 1rem 0;
}
.progress {
width: 100%;
height: 4px;
background: #e2e8f0;
border-radius: 2px;
overflow: hidden;
}
.progress-bar {
height: 100%;
background: #3b82f6;
transition: width 0.3s ease;
}
</style>
Type Definitions
Mutation Options
interface MutationOptions {
timestamps?:
| boolean
| {
createdAt?: string;
updatedAt?: string;
};
validate?: boolean;
schema?: ValidationSchema;
validator?: (data: any) => any;
validateFields?: string[];
retry?: {
enabled?: boolean;
maxAttempts?: number;
delay?: number;
backoff?: 'linear' | 'exponential';
};
analytics?: {
enabled?: boolean;
operationName?: string;
trackPerformance?: boolean;
};
requireConfirmation?: boolean;
confirmationMessage?: string;
merge?: boolean;
}
Validation Schema
interface ValidationSchema {
[key: string]: {
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
required?: boolean;
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
pattern?: RegExp;
default?: any;
validator?: (value: any) => boolean | string;
};
}
Batch Options
interface BatchOptions extends MutationOptions {
maxOperations?: number;
analytics?: {
enabled?: boolean;
operationName?: string;
};
}
Best Practices
Performance
Use batch operations for multiple documents
// Good: Use batch for multiple operations const batch = firekitDocMutations.batch(); users.forEach((user) => batch.add('users', user)); await batch.commit(); // Avoid: Multiple individual operations for (const user of users) { await firekitDocMutations.add('users', user); }
Implement proper validation
// Always validate user input await firekitDocMutations.add('users', userData, { validate: true, schema: userSchema });
Use transactions for related operations
// Use transactions when operations depend on each other await firekitDocMutations.runTransaction(async (transaction) => { // Related operations here });
Error Handling
Handle specific error types
try { await firekitDocMutations.add('users', userData); } catch (error) { switch (error.code) { case 'permission-denied': // Handle permission error break; case 'not-found': // Handle not found error break; default: // Handle other errors } }
Implement retry logic
// Use retry for transient errors await firekitDocMutations.add('users', userData, { retry: { enabled: true, maxAttempts: 3 } });
Data Integrity
Use timestamps for tracking
// Always include timestamps for important data await firekitDocMutations.add('posts', postData, { timestamps: true });
Validate data before writing
// Validate all user input const validatedData = validateUserInput(rawData); await firekitDocMutations.add('users', validatedData);
API Reference
Core Methods
add(collection, data, options?)
- Create new documentset(path, data, options?)
- Set document dataupdate(path, data, options?)
- Update document fieldsdelete(path, options?)
- Delete documentget(path)
- Get single documentgetAll(collection, ids)
- Get multiple documents
Batch Operations
batch(options?)
- Create batch operationbatch.add(collection, data)
- Add to batchbatch.update(path, data)
- Update in batchbatch.delete(path)
- Delete in batchbatch.commit()
- Execute batch
Transaction Operations
runTransaction(updateFunction)
- Run transactiontransaction.get(path)
- Get in transactiontransaction.set(path, data)
- Set in transactiontransaction.update(path, data)
- Update in transactiontransaction.delete(path)
- Delete in transaction
Utility Methods
arrayUnion(...elements)
- Array union operationarrayRemove(...elements)
- Array remove operationincrement(value)
- Increment operationserverTimestamp()
- Server timestamp
Next Steps
- Collection Service - Firestore collection management
- Document Service - Real-time document subscriptions
- Storage Service - File upload/download
- Presence Service - User online/offline tracking
- Analytics Service - Event tracking