Skip to main content
Version: 0.9.0 (Latest)

️🧩 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 location
  • parentId: 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 creation
  • ancestors: 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 locations
  • locationId: 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 findOne with ID filter and projection excluding _id field
  • Output: Location document without internal MongoDB fields
  • Errors: LocationNotFoundBlockError if location doesn't exist, LocationUnexpectedDBError for database failures

Key Features:

  • Optimized Queries: Uses projection to exclude internal _id field
  • 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 locations
  • location: 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: LocationUnexpectedDBError for 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 locations
  • location: Record<string, unknown> - Partial location data to update
  • locationId: 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: LocationNotFoundBlockError if location doesn't exist, LocationUnexpectedDBError for database failures or no modifications

Key Features:

  • Partial Updates: Update any combination of location fields without affecting others
  • Automatic Timestamps: Updates updatedAt field 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 $in operator 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 $in operator
  • 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: LocationConflictError when 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 documents
  • locationId: 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 deleteOne operation, validates successful deletion
  • Output: Success boolean (true) if deleted, or specific error details
  • Errors: LocationUnexpectedDBError for 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 documents
  • filter: 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 find query with projection excluding _id field, converts cursor to array
  • Output: Array of location documents matching the filter with clean field structure
  • Errors: LocationUnexpectedDBError on database query failures

Key Features:

  • Automatic Field Projection: Excludes internal MongoDB _id field 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 parentId to parent field
  • Output: Clean location object with API-friendly field names and structure
  • Errors: Never fails (Result with no error type)

Key Features:

  • Field Transformation: Converts parentId to parent for cleaner API design
  • Internal Field Removal: Removes MongoDB _id and ancestors from 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'));
}