DataBinder provides several utility modules for validation, sanitization, logging, retry logic, and telemetry.
The validation module provides Zod-based schemas and utilities for input validation.
Validates input against a Zod schema and returns the validated data.
import { validateInput } from '@statuscompliance/databinder';
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(0).max(120)
});
try {
const validatedUser = validateInput(userData, userSchema);
// Use validatedUser - it's now type-safe
} catch (error) {
// Handle validation error
console.error('Validation failed:', error.message);
}Validates authentication override options.
const authData = validateInput(authOptions, authOverrideSchema);Validates base fetch options used across datasources.
const fetchOptions = validateInput(options, baseFetchOptionsSchema);Validates pagination configuration.
const paginationConfig = validateInput(paginationOptions, paginationOptionsSchema);Validates query options including filters and sorting.
const queryConfig = validateInput(queryOptions, queryOptionsSchema);You can create custom validators for your datasources:
import { z } from 'zod';
import { validateInput } from '@statuscompliance/databinder';
const customConfigSchema = z.object({
apiKey: z.string().min(1, 'API key is required'),
baseUrl: z.string().url('Must be a valid URL'),
timeout: z.number().min(1000).max(60000).optional(),
retryAttempts: z.number().min(0).max(5).default(3)
});
// Use in your datasource
const validatedConfig = validateInput(config, customConfigSchema);The sanitization module provides functions to clean user input and prevent injection attacks.
Sanitizes strings for safe use in URLs and file paths.
import { sanitizeString } from '@statuscompliance/databinder';
const userInput = "user input with <script>alert('xss')</script>";
const safe = sanitizeString(userInput);
// Result: "user input with scriptalert('xss')/script"Sanitizes filenames for safe file system operations.
import { sanitizeFilename } from '@statuscompliance/databinder';
const filename = "../../../etc/passwd";
const safeFilename = sanitizeFilename(filename);
// Result: "etcpasswd"Sanitizes file paths to prevent directory traversal attacks.
import { sanitizePath } from '@statuscompliance/databinder';
const userPath = "/safe/path/../../../etc/passwd";
const safePath = sanitizePath(userPath);
// Result: "/safe/path/etc/passwd" (traversal attempts removed)Sanitizes URLs to prevent malicious redirects.
import { sanitizeUrl } from '@statuscompliance/databinder';
const userUrl = "javascript:alert('xss')";
const safeUrl = sanitizeUrl(userUrl);
// Result: "about:blank" (dangerous protocols blocked)All built-in datasources automatically sanitize inputs:
// Datasource internally sanitizes the ID
const datasource = catalog.getDatasourceInstance(userProvidedId);
// URLs and paths are sanitized in REST API calls
const result = await dataBinder.fetchFromDatasource('api', {
endpoint: userProvidedEndpoint // Automatically sanitized
});Structured logging with configurable levels and output formats.
import { logger } from '@statuscompliance/databinder';
logger.info('Application started', { version: '1.0.0' });
logger.warn('Deprecated feature used', { feature: 'oldMethod' });
logger.error('Request failed', { error: error.message, statusCode: 500 });
logger.debug('Debug information', { debugData });import { logError } from '@statuscompliance/databinder';
try {
// Some operation
} catch (error) {
logError(error, {
operation: 'fetchData',
datasourceId: 'api-1',
additionalContext: 'Custom context'
});
}Configure logging behavior via environment variables:
# Log level (error, warn, info, debug)
LOG_LEVEL=info
# Log format (json, simple)
LOG_FORMAT=json
# Enable/disable console output
LOG_CONSOLE=true
# Log file path (optional)
LOG_FILE=./logs/databinder.logAutomatic retry functionality with exponential backoff for handling transient failures.
import { withRetry } from '@statuscompliance/databinder';
const result = await withRetry(
async () => {
// Operation that might fail
return await fetch('https://api.example.com/data');
},
{
maxAttempts: 3,
baseDelay: 1000, // 1 second base delay
maxDelay: 10000, // Maximum 10 seconds
backoffFactor: 2, // Exponential backoff
retryCondition: (error) => {
// Retry on network errors and 5xx responses
return error.name === 'NetworkError' ||
(error.status >= 500 && error.status < 600);
}
}
);Built-in datasources use these default retry settings:
{
maxAttempts: 3,
baseDelay: 1000,
maxDelay: 30000,
backoffFactor: 2,
retryCondition: (error) => isRetryableError(error)
}Create custom retry configurations for specific use cases:
import { withRetry, RetryOptions } from '@statuscompliance/databinder';
const customRetryConfig: RetryOptions = {
maxAttempts: 5,
baseDelay: 500,
maxDelay: 15000,
backoffFactor: 1.5,
retryCondition: (error) => {
// Only retry on timeout errors
return error.name === 'TimeoutError';
},
onRetry: (error, attempt) => {
console.log(`Retry attempt ${attempt} after error:`, error.message);
}
};
const result = await withRetry(riskyOperation, customRetryConfig);OpenTelemetry integration for observability and monitoring.
DataBinder automatically creates spans for:
- DataBinder operations (
fetchAll,fetchFromDatasource) - Individual datasource method calls
- Network requests
- Database operations (when using persistence)
Add custom spans to your code:
import { withSpan, SpanKind } from '@statuscompliance/databinder';
const result = await withSpan('custom-operation', async (span) => {
span.setAttribute('operation.type', 'data-processing');
span.setAttribute('items.count', items.length);
// Your operation
const processed = processData(items);
span.setAttribute('processed.count', processed.length);
return processed;
}, {
kind: SpanKind.INTERNAL,
attributes: {
'service.name': 'my-service'
}
});Record custom metrics:
import { recordOperation } from '@statuscompliance/databinder';
const startTime = Date.now();
let success = false;
try {
await performOperation();
success = true;
} catch (error) {
// Handle error
} finally {
const duration = Date.now() - startTime;
recordOperation('custom-operation', success, duration, {
operationType: 'data-fetch',
itemCount: 100
});
}Configure telemetry via environment variables:
# Enable/disable telemetry
OTEL_SDK_DISABLED=false
# Service name
OTEL_SERVICE_NAME=databinder-app
# Exporter type (console, jaeger, otlp)
OTEL_EXPORTER_TYPE=console
# Sampling rate (0.0 to 1.0)
OTEL_SAMPLING_RATIO=1.0Common utility types used throughout DataBinder.
interface AuthOverride {
type?: string;
token?: string;
username?: string;
password?: string;
headerValue?: string;
cookies?: Record<string, string>;
}interface RetryOptions {
maxAttempts: number;
baseDelay: number;
maxDelay: number;
backoffFactor: number;
retryCondition?: (error: Error) => boolean;
onRetry?: (error: Error, attempt: number) => void;
}interface SpanOptions {
kind?: SpanKind;
attributes?: Record<string, string | number | boolean>;
}- Always validate external input using provided schemas
- Create custom schemas for domain-specific validation
- Use type-safe validation to catch errors early
- Sanitize all user-provided strings before use
- Be especially careful with file paths and URLs
- Use appropriate sanitization functions for the context
- Use structured logging with context objects
- Include relevant identifiers in log messages
- Use appropriate log levels (debug for development, info for important events)
- Use retry logic for network operations and external API calls
- Configure retry conditions based on error types
- Set reasonable limits to avoid infinite retry loops
- Use telemetry for monitoring and debugging
- Add relevant attributes to spans
- Configure sampling for production environments