️🧩 Location Blocks
Location blocks provide pure business logic functions for location operations in NodeBlocks applications. These blocks contain the core application logic and are designed to be used with applyPayloadArgs for payload context lifting.
🎯 Overview
Location blocks are designed to:
- Separate business logic from payload handling
- Provide pure functions that take only required data
- Enable easy testing with isolated logic
- Support composition with payload context lifting
- Return Result types for proper error handling
- Handle hierarchical relationships with ancestor tracking
📋 Location Block Types
Ancestor & Hierarchy Management Blocks
Pure functions for building and managing location hierarchies.
Database Operations Blocks
Pure functions for location CRUD operations.
Data Normalization Blocks
Pure functions for transforming location data for API responses.
🔧 Available Location Blocks
buildAncestorsFromParent
Builds ancestor path by appending parent ID to existing ancestors array.
Purpose: Constructs the complete ancestor chain for hierarchical location relationships.
Parameters:
ancestorsOfParent: string[]- Array of ancestor IDs from the parent locationparentId: string- ID of the parent location to append to ancestors
Returns: Promise<Result<string[], never>> - Result with updated ancestors array
Handler Process:
- Input: Parent's ancestor array and parent ID
- Process: Spreads existing ancestors and appends the parent ID using string conversion
- Output: New ancestors array including the parent location
- Errors: Never fails (always returns ok result)
Key Features:
- Immutable Operations: Creates new array without modifying input
- Type Safety: Ensures all ancestor IDs are strings
- Hierarchical Integrity: Maintains proper ancestor ordering
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Building ancestors for a building under a city under a region
const ancestors = (
await blocks.buildAncestorsFromParent(['org-1', 'region-2'], 'city-3')
)._unsafeUnwrap();
// ancestors => ['org-1', 'region-2', 'city-3']
buildLocationToCreate
Builds location data for creation with normalized ancestors and parent ID.
Purpose: Prepares location data for database insertion with proper hierarchical relationships and normalization.
Parameters:
location: Record<string, unknown>- Raw location data to normalize for creationancestors: string[]- Array of ancestor IDs (defaults to empty array)
Returns: Promise<Result<Record<string, unknown>, never>> - Result with normalized location data
Handler Process:
- Input: Raw location data and optional ancestors array
- Process: Spreads location data, adds ancestors array, normalizes parentId to null if undefined
- Output: Normalized location data ready for database insertion
- Errors: Never fails (always returns ok result)
Key Features:
- Data Normalization: Ensures consistent field types and values
- Parent Handling: Converts undefined parentId to null for database consistency
- Ancestor Integration: Seamlessly incorporates ancestor chains
- Flexible Input: Accepts various location data formats
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
const locationData = (
await blocks.buildLocationToCreate(
{
name: 'Headquarters Building',
code: 'HQ-001',
type: 'BUILDING',
parentId: 'city-123'
},
['org-1', 'region-2', 'city-123']
)
)._unsafeUnwrap();
// Result: { name: 'Headquarters Building', code: 'HQ-001', type: 'BUILDING',
// parentId: 'city-123', ancestors: ['org-1', 'region-2', 'city-123'] }
getLocationById
Retrieves a location by id from the database with proper field projection.
Purpose: Fetches a single location document by ID with optimized field selection for API responses.
Parameters:
locationsCollection: Collection- MongoDB collection containing locationslocationId: string- Identifier of the location to retrieve
Returns: Promise<Result<Record<string, unknown>, LocationBlockError>> - Result with location document or error
Handler Process:
- Input: Database collection and location ID string
- Process: Executes
findOnewith ID filter and projection excluding_idfield - Output: Location document without internal MongoDB fields
- Errors:
LocationNotFoundBlockErrorif location doesn't exist,LocationUnexpectedDBErrorfor database failures
Key Features:
- Optimized Queries: Uses projection to exclude internal
_idfield - Type Safety: Ensures locationId is converted to string for MongoDB compatibility
- Error Specificity: Provides clear error messages for different failure scenarios
- API Ready: Returns data suitable for JSON API responses
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
const location = (
await blocks.getLocationById(db.locations, 'loc-123')
)._unsafeUnwrap();
// Returns location object without _id field, ready for API response
console.log(location); // { id: 'loc-123', name: 'HQ', ancestors: [...], ... }
createLocation
Creates a new location document in the database with base entity normalization.
Purpose: Persists a new location to the database with automatic ID generation and timestamp handling.
Parameters:
locationsCollection: Collection- MongoDB collection containing locationslocation: Record<string, unknown>- Raw location data to persist
Returns: Promise<Result<string, LocationBlockError>> - Result with created location ID or error
Handler Process:
- Input: Database collection and location data object
- Process: Creates base entity with timestamps and UUID, inserts into collection, validates insertion
- Output: Generated location ID string for reference
- Errors:
LocationUnexpectedDBErrorfor insertion failures or unexpected database errors
Key Features:
- Automatic ID Generation: Creates unique UUID for each location
- Timestamp Management: Adds createdAt and updatedAt timestamps
- Atomic Operations: Single database transaction for consistency
- Validation: Ensures successful insertion before returning
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
const locationId = (
await blocks.createLocation(db.locations, {
name: 'Regional Office',
code: 'REG-001',
type: 'REGION',
ancestors: [],
parentId: null
})
)._unsafeUnwrap();
// locationId contains the generated UUID for the new location
console.log(locationId); // '550e8400-e29b-41d4-a716-446655440000'
updateLocation
Updates an existing location document by ID with partial field updates.
Purpose: Modifies location data with automatic timestamp updates and validation, supporting partial updates of any location fields.
Parameters:
locationsCollection: Collection- MongoDB collection for locationslocation: Record<string, unknown>- Partial location data to updatelocationId: string- Unique identifier of the location to update
Returns: Promise<Result<boolean, LocationBlockError>>
Handler Process:
- Input: Database collection, partial update data, and location ID
- Process: Updates location document using MongoDB updateOne with normalized fields and automatic timestamp updates
- Output: Success boolean (true) when update completes successfully
- Errors:
LocationNotFoundBlockErrorif location doesn't exist,LocationUnexpectedDBErrorfor database failures or no modifications
Key Features:
- Partial Updates: Update any combination of location fields without affecting others
- Automatic Timestamps: Updates
updatedAtfield automatically via base entity normalization - Validation: Ensures location exists before attempting update
- Atomic Operations: Single database operation ensures consistency
- Error Specificity: Clear error messages for different failure scenarios
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
const result = await blocks.updateLocation(
locationsCollection,
{
name: 'Updated Headquarters',
code: 'HQ-UPDATED'
},
'loc-123'
);
if (result.isOk()) {
console.log('Location updated successfully');
}
Usage Patterns
Creating Hierarchical Locations
import { blocks } from '@nodeblocks/backend-sdk';
// 1. Create root location (organization)
const orgData = await blocks.buildLocationToCreate({
name: 'Global Corp',
code: 'GLOBAL',
type: 'ORGANIZATION'
});
const orgId = await blocks.createLocation(db.locations, orgData);
// 2. Create child location (region) with ancestors
const regionData = await blocks.buildLocationToCreate({
name: 'Asia Pacific',
code: 'APAC',
type: 'REGION',
parentId: orgId
}, [orgId]);
const regionId = await blocks.createLocation(db.locations, regionData);
// 3. Create grandchild location (city) with full ancestor chain
const cityAncestors = await blocks.buildAncestorsFromParent([orgId], regionId);
const cityData = await blocks.buildLocationToCreate({
name: 'Tokyo',
code: 'TYO',
type: 'CITY',
parentId: regionId
}, cityAncestors);
const cityId = await blocks.createLocation(db.locations, cityData);
Retrieving Locations with Hierarchy
import { blocks } from '@nodeblocks/backend-sdk';
// Get location with full hierarchy information
const location = await blocks.getLocationById(db.locations, 'loc-123');
// Access hierarchical data
console.log(location.ancestors); // ['org-1', 'region-2']
console.log(location.parentId); // 'region-2'
console.log(location.type); // 'CITY'
buildDescendantsFilter
Builds MongoDB filter to find locations with specified ancestor in their hierarchy.
Purpose: Creates database query filters for finding all descendant locations within a hierarchical structure, enabling validation and cleanup operations.
Parameters:
locationId: string- The location ID to search for in ancestors hierarchy
Returns: Promise<Result<Record<string, unknown>, never>> with MongoDB filter object
Handler Process:
- Input: Location ID string to search for in hierarchy
- Process: Creates MongoDB filter using
$inoperator for ancestors array matching - Output: Filter object for finding locations containing the locationId in ancestors
- Errors: None (never returns error)
Key Features:
- Hierarchy Queries: Enables finding all child locations in the hierarchy
- Database Optimization: Uses efficient MongoDB
$inoperator - Validation Support: Foundation for hierarchy constraint validation
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Build filter to find all descendants of a parent location
const filter = await blocks.buildDescendantsFilter('parent-123');
const descendants = await blocks.findLocations(db.locations, filter.value);
// Result: [{ id: 'child-1', ancestors: ['parent-123'] }, ...]
assertNoDescendantLocations
Validates that no child locations exist before allowing parent location deletion.
Purpose: Enforces data integrity by preventing deletion of parent locations that have dependent child locations, maintaining hierarchical consistency.
Parameters:
childLocations: Record<string, unknown>[]- Array of child location documents to validate for conflicts
Returns: Promise<Result<boolean, LocationBlockError>> with validation result
Handler Process:
- Input: Array of potential child location documents
- Process: Checks array length to determine if any children exist
- Output: Success (true) if no children, or LocationConflictError if children found
- Errors:
LocationConflictErrorwhen child locations are present
Key Features:
- Data Integrity: Prevents orphaned child locations
- Hierarchy Protection: Maintains referential integrity in location trees
- Clear Error Messages: Provides specific conflict information
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Validate before deletion
const childFilter = await blocks.buildDescendantsFilter('parent-123');
const children = await blocks.findLocations(db.locations, childFilter.value);
const canDelete = await blocks.assertNoDescendantLocations(children.value);
if (canDelete.isErr()) {
// Handle conflict - cannot delete parent with children
console.log(canDelete.error.message); // "Dependent child locations still exist"
return;
}
// Safe to delete parent location
deleteLocation
Deletes a location from the database by ID.
Purpose: Safely removes location documents from the database with proper validation and error handling.
Parameters:
locationsCollection: Collection- MongoDB collection containing location documentslocationId: string- Unique identifier of the location to delete
Returns: Promise<Result<boolean, LocationBlockError>> with deletion success status
Handler Process:
- Input: Database collection and location ID string
- Process: Executes MongoDB
deleteOneoperation, validates successful deletion - Output: Success boolean (true) if deleted, or specific error details
- Errors:
LocationUnexpectedDBErrorfor database failures or zero deletions
Key Features:
- Atomic Operations: Single database operation ensures consistency
- Validation: Confirms successful deletion before returning success
- Error Specificity: Differentiates between database errors and not found scenarios
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Delete location after validation
const deleteResult = await blocks.deleteLocation(db.locations, 'loc-123');
if (deleteResult.isOk()) {
console.log('Location deleted successfully');
} else {
console.log('Deletion failed:', deleteResult.error.message);
}
findLocations
Retrieves multiple locations matching a filter from the database with automatic field projection.
Purpose: Provides flexible querying capabilities for location collections, supporting various filter criteria for bulk operations and hierarchy queries with automatic exclusion of internal MongoDB fields.
Parameters:
locationsCollection: Collection- MongoDB collection containing location documentsfilter: Record<string, unknown>- Query filter object to match locations
Returns: Promise<Result<Record<string, unknown>[], LocationBlockError>> with array of matching locations
Handler Process:
- Input: Database collection and filter criteria
- Process: Executes MongoDB
findquery with projection excluding_idfield, converts cursor to array - Output: Array of location documents matching the filter with clean field structure
- Errors:
LocationUnexpectedDBErroron database query failures
Key Features:
- Automatic Field Projection: Excludes internal MongoDB
_idfield from results - Flexible Filtering: Supports any MongoDB query filter for complex queries
- Bulk Operations: Efficient retrieval of multiple locations
- Hierarchy Queries: Enables ancestor and descendant queries
- Clean Data: Returns API-ready location objects without internal fields
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Find all locations of a specific type
const typeFilter = { type: 'BUILDING' };
const buildings = await blocks.findLocations(db.locations, typeFilter);
// Find locations by ancestor (descendants)
const ancestorFilter = await blocks.buildDescendantsFilter('parent-123');
const descendants = await blocks.findLocations(db.locations, ancestorFilter.value);
// Find locations by multiple criteria
const complexFilter = {
type: 'REGION',
ancestors: { $in: ['org-456'] }
};
const regions = await blocks.findLocations(db.locations, complexFilter);
Complete Location Deletion Workflow
Safe Location Deletion Pattern
import { blocks } from '@nodeblocks/backend-sdk';
async function deleteLocationSafely(locationId: string) {
// 1. Build filter to find child locations
const childFilter = await blocks.buildDescendantsFilter(locationId);
// 2. Check for dependent children
const children = await blocks.findLocations(db.locations, childFilter.value);
// 3. Validate no children exist
const canDelete = await blocks.assertNoDescendantLocations(children.value);
if (canDelete.isErr()) {
return canDelete; // Cannot delete - has dependent children
}
// 4. Safe to delete
return await blocks.deleteLocation(db.locations, locationId);
}
// Usage
const result = await deleteLocationSafely('parent-location-123');
if (result.isOk()) {
console.log('Location and hierarchy validated successfully');
}
Hierarchical Query Patterns
import { blocks } from '@nodeblocks/backend-sdk';
// Get entire subtree under a location
async function getLocationSubtree(rootId: string) {
const filter = await blocks.buildDescendantsFilter(rootId);
const descendants = await blocks.findLocations(db.locations, filter.value);
// Include root location
const root = await blocks.getLocationById(db.locations, rootId);
return [root.value, ...descendants.value];
}
// Get all locations at a specific level
async function getLocationsByType(locationType: string) {
return await blocks.findLocations(db.locations, { type: locationType });
}
// Get locations by ancestor path
async function getLocationsByAncestor(ancestorId: string) {
const filter = await blocks.buildDescendantsFilter(ancestorId);
return await blocks.findLocations(db.locations, filter.value);
}
normalizeLocation
Normalizes location data by transforming internal fields to API-friendly format.
Purpose: Transforms location data from internal MongoDB format to clean API response format, removing internal fields and converting field names for better API design.
Parameters:
location: Record<string, unknown> & { _id: string; parentId: string; ancestors: string[] }- Location object containing internal MongoDB fields and custom data
Returns: Result<Record<string, unknown>, never> with normalized location data
Handler Process:
- Input: Location object with internal MongoDB fields (
_id,parentId,ancestors) and custom data - Process: Destructures and removes internal fields, transforms
parentIdtoparentfield - Output: Clean location object with API-friendly field names and structure
- Errors: Never fails (Result with no error type)
Key Features:
- Field Transformation: Converts
parentIdtoparentfor cleaner API design - Internal Field Removal: Removes MongoDB
_idandancestorsfrom API responses - Data Preservation: Maintains all custom location fields and properties
- API Consistency: Provides consistent field naming across location responses
- Safe Operation: Pure function with no side effects or failures
Example Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Transform location for API response
const normalized = blocks.normalizeLocation({
_id: "507f1f77bcf86cd799439011",
id: "loc-123",
parentId: "parent-456",
ancestors: ["org-789", "region-012", "parent-456"],
name: "Headquarters",
code: "HQ",
type: "BUILDING"
});
// Result: { id: "loc-123", parent: "parent-456", name: "Headquarters", code: "HQ", type: "BUILDING" }
console.log(normalized.value); // Clean API response object
Complete Location Workflow with Normalization
API Response Preparation Pattern
import { blocks } from '@nodeblocks/backend-sdk';
async function getNormalizedLocation(locationId: string) {
// 1. Retrieve raw location from database
const rawLocation = await blocks.getLocationById(db.locations, locationId);
if (rawLocation.isErr()) {
return rawLocation; // Propagate error
}
// 2. Normalize for API response
const normalized = blocks.normalizeLocation(rawLocation.value);
return normalized;
}
// Usage in API handler
const result = await getNormalizedLocation('loc-123');
if (result.isOk()) {
// Send clean response to client
res.json(result.value);
}
Bulk Normalization Pattern
import { blocks } from '@nodeblocks/backend-sdk';
async function getNormalizedLocationList(filter: Record<string, unknown>) {
// 1. Find locations with automatic projection
const locations = await blocks.findLocations(db.locations, filter);
if (locations.isErr()) {
return locations; // Propagate error
}
// 2. Normalize each location for API response
const normalizedLocations = locations.value.map(location =>
blocks.normalizeLocation(location).value
);
return ok(normalizedLocations);
}
// Usage for location search endpoints
const result = await getNormalizedLocationList({ type: 'BUILDING' });
if (result.isOk()) {
// Send normalized location array to client
res.json({ data: result.value });
}
🚨 Error Classes
LocationBlockError
Base error class for all location-related block operations.
Purpose: Provides a common base class for location-specific errors with proper error hierarchy.
Error Hierarchy:
- LocationBlockError: Base class for all location block errors
- LocationNotFoundBlockError: Thrown when a location cannot be found
- LocationUnexpectedDBError: Thrown when database operations fail unexpectedly
Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Used as base class for specific location errors
class CustomLocationError extends blocks.LocationBlockError {
constructor(message: string) {
super(message, 'CustomLocationError');
}
}
LocationNotFoundBlockError
Error thrown when a requested location cannot be found in the database.
Purpose: Specific error for location not found scenarios during retrieval operations.
Usage:
if (!location) {
return err(new LocationNotFoundBlockError('Location not found'));
}
LocationUnexpectedDBError
Error thrown when location database operations fail unexpectedly.
Purpose: Handles unexpected database failures during location operations.
Usage:
try {
await locationsCollection.insertOne(location);
} catch (_error) {
return err(new LocationUnexpectedDBError('Failed to create location'));
}
LocationConflictError
Error thrown when location operations conflict with existing dependencies.
Purpose: Handles conflicts that arise from location hierarchy constraints, particularly when attempting to delete parent locations that have dependent child locations.
Usage:
import { blocks } from '@nodeblocks/backend-sdk';
// Thrown in location deletion validation:
if (childLocations.length > 0) {
return err(new blocks.LocationConflictError('Dependent child locations still exist'));
}
🔗 Related Documentation
- Location Schemas - Location validation schemas