The dot notation module provides utilities for working with Firestore’s dot notation syntax, which allows updating nested fields without replacing entire objects.
Core Functions
isDotNotation
Checks if a key uses dot notation syntax.
function isDotNotation(key: string): boolean
Parameters
The key to check for dot notation
Returns
Returns true if the key contains a dot (.) character, false otherwise
Example
import { isDotNotation } from '@spacelabstech/firestoreorm' // dotNotation';
isDotNotation('address.city'); // true
isDotNotation('name'); // false
isDotNotation('user.profile.bio'); // true
hasDotNotationKeys
Checks if an object contains any keys with dot notation.
function hasDotNotationKeys(obj: Record<string, any>): boolean
Parameters
obj
Record<string, any>
required
The object to check for dot notation keys
Returns
Returns true if any key in the object uses dot notation, false otherwise
Example
import { hasDotNotationKeys } from '@spacelabstech/firestoreorm' // dotNotation';
hasDotNotationKeys({
name: 'John',
'address.city': 'LA'
}); // true
hasDotNotationKeys({
name: 'John',
age: 30
}); // false
Conversion Functions
expandDotNotation
Converts a flat object with dot notation keys into a nested object structure.
function expandDotNotation<T = any>(
flatObj: Record<string, any>
): T
Parameters
flatObj
Record<string, any>
required
Object with dot notation keys to expand into nested structure
Returns
Nested object structure with expanded dot notation paths
Example
import { expandDotNotation } from '@spacelabstech/firestoreorm' // dotNotation';
const flat = {
'address.city': 'Los Angeles',
'address.zip': '90001',
'address.country': 'USA',
name: 'John Doe'
};
const nested = expandDotNotation(flat);
// Result:
// {
// address: {
// city: 'Los Angeles',
// zip: '90001',
// country: 'USA'
// },
// name: 'John Doe'
// }
flattenToDotNotation
Converts a nested object into a flat object with dot notation keys.
function flattenToDotNotation<T = any>(
obj: Record<string, any>,
prefix?: string
): Record<string, any>
Parameters
obj
Record<string, any>
required
Nested object to flatten into dot notation
Internal prefix for recursion. Leave empty when calling directly.
Returns
Flat object with dot notation keys
Behavior
- Only flattens plain objects
- Preserves arrays, dates, and other special types as values
- Does not flatten objects with custom prototypes
Example
import { flattenToDotNotation } from '@spacelabstech/firestoreorm' // dotNotation';
const nested = {
address: {
city: 'Los Angeles',
zip: '90001',
country: 'USA'
},
name: 'John Doe',
tags: ['customer', 'premium'],
createdAt: new Date('2024-01-01')
};
const flat = flattenToDotNotation(nested);
// Result:
// {
// 'address.city': 'Los Angeles',
// 'address.zip': '90001',
// 'address.country': 'USA',
// name: 'John Doe',
// tags: ['customer', 'premium'],
// createdAt: Date(2024-01-01)
// }
Update Functions
mergeDotNotationUpdate
Merges dot notation updates with existing data, handling both regular and dot notation keys.
function mergeDotNotationUpdate(
existingData: Record<string, any>,
updates: Record<string, any>
): Record<string, any>
Parameters
existingData
Record<string, any>
required
Current document data
updates
Record<string, any>
required
Updates to apply (may contain dot notation keys)
Returns
Merged data structure with updates applied
Behavior
- Skips undefined values (Firestore doesn’t accept them)
- Handles mixed regular and dot notation keys
- Creates nested objects as needed
- Preserves existing data not affected by updates
Example
import { mergeDotNotationUpdate } from '@spacelabstech/firestoreorm' // dotNotation';
const existing = {
name: 'John Doe',
address: {
city: 'New York',
zip: '10001',
country: 'USA'
},
age: 30
};
const updates = {
'address.city': 'Los Angeles',
'address.zip': '90001',
age: 31
};
const result = mergeDotNotationUpdate(existing, updates);
// Result:
// {
// name: 'John Doe',
// address: {
// city: 'Los Angeles',
// zip: '90001',
// country: 'USA'
// },
// age: 31
// }
Validation Functions
validateDotNotationPath
Validates dot notation paths to prevent invalid field names.
function validateDotNotationPath(key: string): void
Parameters
The dot notation key to validate
Throws
Throws an Error if the key is invalid:
- Empty or whitespace-only paths
- Paths starting or ending with a dot
- Paths with consecutive dots (empty parts)
Example
import { validateDotNotationPath } from '@spacelabstech/firestoreorm' // dotNotation';
// Valid paths
validateDotNotationPath('address.city'); // OK
validateDotNotationPath('user.profile.bio'); // OK
validateDotNotationPath('name'); // OK
// Invalid paths
validateDotNotationPath(''); // Error: cannot be empty
validateDotNotationPath('.address.city'); // Error: cannot start with dot
validateDotNotationPath('address.city.'); // Error: cannot end with dot
validateDotNotationPath('address..city'); // Error: empty parts
Utility Functions
getRootFields
Extracts the root fields from an array of dot notation paths.
function getRootFields(keys: string[]): string[]
Parameters
Array of keys (may include dot notation)
Returns
Array of unique root field names
Example
import { getRootFields } from '@spacelabstech/firestoreorm' // dotNotation';
const keys = [
'address.city',
'address.zip',
'address.country',
'name',
'profile.bio',
'profile.avatar'
];
const roots = getRootFields(keys);
// Result: ['address', 'name', 'profile']
getDotNotationDepth
Gets the depth of a dot notation path.
function getDotNotationDepth(key: string): number
Parameters
Returns
The depth of the path (number of segments)
Example
import { getDotNotationDepth } from '@spacelabstech/firestoreorm' // dotNotation';
getDotNotationDepth('name'); // 1
getDotNotationDepth('address.city'); // 2
getDotNotationDepth('user.profile.settings.theme'); // 4
Complete Usage Example
import {
expandDotNotation,
flattenToDotNotation,
mergeDotNotationUpdate,
validateDotNotationPath,
getRootFields,
getDotNotationDepth
} from '@spacelabstech/firestoreorm' // dotNotation';
// Updating nested fields with dot notation
const userUpdate = {
'profile.bio': 'Software Engineer',
'profile.social.twitter': '@johndoe',
'settings.notifications.email': true
};
// Validate all paths
for (const key of Object.keys(userUpdate)) {
validateDotNotationPath(key);
}
// Get affected root fields
const affectedFields = getRootFields(Object.keys(userUpdate));
console.log('Updating fields:', affectedFields);
// ['profile', 'settings']
// Expand to nested structure for local processing
const expanded = expandDotNotation(userUpdate);
console.log(expanded);
// {
// profile: {
// bio: 'Software Engineer',
// social: { twitter: '@johndoe' }
// },
// settings: {
// notifications: { email: true }
// }
// }
// Merge with existing data
const existingUser = {
name: 'John Doe',
email: '[email protected]',
profile: {
bio: 'Developer',
avatar: 'avatar.jpg'
},
settings: {
theme: 'dark',
notifications: {
email: false,
push: true
}
}
};
const mergedUser = mergeDotNotationUpdate(existingUser, userUpdate);
console.log(mergedUser);
// {
// name: 'John Doe',
// email: '[email protected]',
// profile: {
// bio: 'Software Engineer',
// avatar: 'avatar.jpg',
// social: { twitter: '@johndoe' }
// },
// settings: {
// theme: 'dark',
// notifications: {
// email: true,
// push: true
// }
// }
// }
// Flatten back to dot notation for Firestore
const flattened = flattenToDotNotation(mergedUser);
console.log(flattened);
Use Cases
Partial Updates Without Overwriting
// Without dot notation - overwrites entire address
await userRepo.update(userId, {
address: { city: 'LA' }
});
// Result: { address: { city: 'LA' } } - zip and country are lost!
// With dot notation - updates only city
await userRepo.update(userId, {
'address.city': 'LA'
});
// Result: { address: { city: 'LA', zip: '90001', country: 'USA' } }
Batch Updates on Nested Fields
const settingsUpdates = {
'settings.theme': 'dark',
'settings.language': 'en',
'settings.notifications.email': true,
'settings.notifications.sms': false
};
await userRepo.update(userId, settingsUpdates);
// Form fields using dot notation
const formData = {
'profile.firstName': req.body.firstName,
'profile.lastName': req.body.lastName,
'profile.bio': req.body.bio,
'settings.timezone': req.body.timezone
};
// Validate and update
for (const key of Object.keys(formData)) {
validateDotNotationPath(key);
}
await userRepo.update(userId, formData);