Development aids including assertion utilities, error handling, debugging tools, and data type utilities.
Runtime assertion and validation utilities for development and debugging.
const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning');
/**
* Assertion utility that throws error if condition is false
* Supports sprintf-style formatting with %s placeholders
* @param condition - Condition to assert (truthy values pass)
* @param format - Error message format string with %s placeholders
* @param args - Values to substitute into format string
* @throws Error with formatted message if condition is false
*/
function invariant(condition: any, format: string, ...args: Array<any>): void;
/**
* Development warning utility (no-op in production builds)
* Logs warning to console if condition is false
* @param condition - Condition to check (false triggers warning)
* @param format - Warning message format string with %s placeholders
* @param args - Values to substitute into format string
*/
function warning(condition: boolean, format: string, ...args: Array<any>): void;Usage Examples:
const invariant = require('fbjs/lib/invariant');
const warning = require('fbjs/lib/warning');
// Basic assertions
function processUser(user) {
invariant(user, 'User object is required');
invariant(user.id, 'User must have an ID');
invariant(typeof user.name === 'string', 'User name must be a string');
// Process user...
}
// Assertions with formatted messages
function transfer(fromAccount, toAccount, amount) {
invariant(fromAccount, 'Source account is required');
invariant(toAccount, 'Destination account is required');
invariant(amount > 0, 'Transfer amount must be positive, got %s', amount);
invariant(fromAccount.balance >= amount,
'Insufficient funds: need %s, have %s', amount, fromAccount.balance);
// Perform transfer...
}
// Array validation
function processArray(items) {
invariant(Array.isArray(items), 'Expected array, got %s', typeof items);
invariant(items.length > 0, 'Array cannot be empty');
items.forEach((item, index) => {
invariant(item != null, 'Item at index %s cannot be null', index);
});
}
// API parameter validation
function createUser(userData) {
invariant(userData, 'User data is required');
invariant(userData.email, 'Email is required');
invariant(userData.email.includes('@'), 'Invalid email format: %s', userData.email);
warning(userData.name, 'User name not provided, using email as display name');
warning(userData.phone, 'Phone number not provided');
return {
id: generateId(),
email: userData.email,
name: userData.name || userData.email,
phone: userData.phone || null
};
}
// State validation
class Counter {
constructor(initialValue = 0) {
invariant(typeof initialValue === 'number',
'Initial value must be a number, got %s', typeof initialValue);
this.value = initialValue;
}
increment(step = 1) {
invariant(typeof step === 'number', 'Step must be a number');
invariant(step > 0, 'Step must be positive, got %s', step);
this.value += step;
warning(this.value <= 1000, 'Counter value is getting large: %s', this.value);
}
decrement(step = 1) {
invariant(typeof step === 'number', 'Step must be a number');
invariant(step > 0, 'Step must be positive, got %s', step);
invariant(this.value >= step, 'Cannot decrement below zero: %s - %s', this.value, step);
this.value -= step;
}
}
// Configuration validation
function initializeApp(config) {
invariant(config, 'Configuration object is required');
invariant(config.apiUrl, 'API URL is required');
invariant(typeof config.timeout === 'number',
'Timeout must be a number, got %s', typeof config.timeout);
warning(config.timeout >= 5000,
'Timeout is very short (%s ms), consider increasing it', config.timeout);
warning(config.apiUrl.startsWith('https://'),
'API URL should use HTTPS: %s', config.apiUrl);
// Initialize app with validated config
}
// Error boundaries with detailed messages
function parseJSON(jsonString) {
invariant(typeof jsonString === 'string',
'JSON input must be string, got %s', typeof jsonString);
invariant(jsonString.length > 0, 'JSON string cannot be empty');
try {
return JSON.parse(jsonString);
} catch (error) {
invariant(false, 'Invalid JSON: %s', error.message);
}
}Utilities for runtime type checking and null safety.
const nullthrows = require('fbjs/lib/nullthrows');
/**
* Throws error if value is null or undefined, returns value otherwise
* Provides type narrowing in TypeScript/Flow
* @param value - Value to check for null/undefined
* @param message - Optional error message
* @returns The value if not null/undefined
* @throws Error if value is null or undefined
*/
function nullthrows<T>(value: ?T, message?: string): T;Usage Examples:
const nullthrows = require('fbjs/lib/nullthrows');
// Basic null checking
function processElement(elementId) {
const element = document.getElementById(elementId);
const safeElement = nullthrows(element, `Element not found: ${elementId}`);
// Now we know element is not null
safeElement.addEventListener('click', handleClick);
return safeElement;
}
// API response validation
function processUserData(response) {
const user = nullthrows(response.user, 'User data missing from response');
const profile = nullthrows(user.profile, 'User profile missing');
return {
id: nullthrows(user.id, 'User ID missing'),
name: nullthrows(profile.name, 'Profile name missing'),
email: nullthrows(profile.email, 'Profile email missing')
};
}
// Array processing with null safety
function processItems(items) {
return items.map((item, index) => {
const safeItem = nullthrows(item, `Item at index ${index} is null`);
return {
id: nullthrows(safeItem.id, `Item ${index} missing ID`),
name: nullthrows(safeItem.name, `Item ${index} missing name`)
};
});
}
// Optional chaining alternative
function getNestedValue(obj) {
const level1 = nullthrows(obj.level1, 'Level 1 missing');
const level2 = nullthrows(level1.level2, 'Level 2 missing');
return nullthrows(level2.value, 'Final value missing');
}
// Function parameter validation
function calculateArea(shape) {
const width = nullthrows(shape.width, 'Shape width is required');
const height = nullthrows(shape.height, 'Shape height is required');
return width * height;
}
// Configuration validation
function loadConfig(configData) {
return {
apiKey: nullthrows(configData.apiKey, 'API key is required'),
baseUrl: nullthrows(configData.baseUrl, 'Base URL is required'),
timeout: configData.timeout || 5000, // Optional with default
retries: configData.retries || 3 // Optional with default
};
}Utilities for creating objects where keys equal their values, useful for constants and enums.
const keyMirror = require('fbjs/lib/keyMirror');
const keyMirrorRecursive = require('fbjs/lib/keyMirrorRecursive');
const keyOf = require('fbjs/lib/keyOf');
/**
* Creates object where keys equal their values
* Useful for creating constant enums that are minification-safe
* Validates that input is an object and not an array using invariant
* @param obj - Object with keys to mirror (values are ignored)
* @returns Object where each key equals its string value
* @throws Error if obj is not an object or is an array
*/
function keyMirror<T: {}>(obj: T): $ObjMapi<T, <K>(K) => K>;
/**
* Recursive version of keyMirror for nested objects
* @param obj - Object with nested structure to mirror
* @returns Object with mirrored keys at all levels
*/
function keyMirrorRecursive(obj: Object): Object;
/**
* Gets minification-safe property key name
* Useful for accessing object properties in a way that survives minification
* @param oneKeyObj - Object with single property
* @returns String key name
*/
function keyOf(oneKeyObj: Object): string;Usage Examples:
const keyMirror = require('fbjs/lib/keyMirror');
const keyMirrorRecursive = require('fbjs/lib/keyMirrorRecursive');
const keyOf = require('fbjs/lib/keyOf');
// Basic enum creation
const ActionTypes = keyMirror({
USER_LOGIN: null,
USER_LOGOUT: null,
LOAD_DATA: null,
SAVE_DATA: null,
SHOW_ERROR: null
});
console.log(ActionTypes);
// {
// USER_LOGIN: 'USER_LOGIN',
// USER_LOGOUT: 'USER_LOGOUT',
// LOAD_DATA: 'LOAD_DATA',
// SAVE_DATA: 'SAVE_DATA',
// SHOW_ERROR: 'SHOW_ERROR'
// }
// Error handling - keyMirror validates input with invariant
try {
const invalidEnum = keyMirror(['not', 'an', 'object']); // Throws Error
} catch (error) {
console.error('keyMirror failed:', error.message);
// Output: "keyMirror(...): Argument must be an object."
}
try {
const arrayEnum = keyMirror([1, 2, 3]); // Throws Error - arrays not allowed
} catch (error) {
console.error('keyMirror failed:', error.message);
// Output: "keyMirror(...): Argument must be an object."
}
// Safe keyMirror wrapper
function safeKeyMirror(obj, defaultValue = {}) {
try {
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
console.warn('Invalid input for keyMirror, using default');
return defaultValue;
}
return keyMirror(obj);
} catch (error) {
console.error('keyMirror error:', error.message);
return defaultValue;
}
}
// Usage in Redux actions
function loginUser(credentials) {
return {
type: ActionTypes.USER_LOGIN,
payload: credentials
};
}
// API endpoints enum
const API_ENDPOINTS = keyMirror({
USERS: null,
PRODUCTS: null,
ORDERS: null,
ANALYTICS: null
});
function makeRequest(endpoint, data) {
const url = `https://api.example.com/${endpoint.toLowerCase()}`;
// Safe to use endpoint as it equals its string value
}
// Status constants
const Status = keyMirror({
PENDING: null,
SUCCESS: null,
ERROR: null,
CANCELLED: null
});
class AsyncOperation {
constructor() {
this.status = Status.PENDING;
}
complete() {
this.status = Status.SUCCESS;
}
fail() {
this.status = Status.ERROR;
}
}
// Recursive mirroring for nested constants
const CONFIG_KEYS = keyMirrorRecursive({
API: {
BASE_URL: null,
TIMEOUT: null,
RETRIES: null
},
UI: {
THEME: null,
LANGUAGE: null,
ANIMATIONS: {
DURATION: null,
EASING: null
}
},
FEATURES: {
ANALYTICS: null,
NOTIFICATIONS: null
}
});
console.log(CONFIG_KEYS);
// {
// API: {
// BASE_URL: 'BASE_URL',
// TIMEOUT: 'TIMEOUT',
// RETRIES: 'RETRIES'
// },
// UI: {
// THEME: 'THEME',
// LANGUAGE: 'LANGUAGE',
// ANIMATIONS: {
// DURATION: 'DURATION',
// EASING: 'EASING'
// }
// },
// FEATURES: {
// ANALYTICS: 'ANALYTICS',
// NOTIFICATIONS: 'NOTIFICATIONS'
// }
// }
// Minification-safe property access
const user = { name: 'Alice', age: 30, email: 'alice@example.com' };
// Instead of 'name' (which gets minified)
const nameKey = keyOf({ name: null }); // 'name'
const userName = user[nameKey];
// Safer for property validation
function validateUser(user) {
const requiredFields = [
keyOf({ name: null }),
keyOf({ email: null }),
keyOf({ age: null })
];
requiredFields.forEach(field => {
if (!user[field]) {
throw new Error(`Missing required field: ${field}`);
}
});
}
// Form field names
const FORM_FIELDS = keyMirror({
USERNAME: null,
PASSWORD: null,
EMAIL: null,
CONFIRM_PASSWORD: null
});
function createForm() {
return {
[FORM_FIELDS.USERNAME]: '',
[FORM_FIELDS.PASSWORD]: '',
[FORM_FIELDS.EMAIL]: '',
[FORM_FIELDS.CONFIRM_PASSWORD]: ''
};
}
// Event types for event emitter
const EVENTS = keyMirror({
CONNECT: null,
DISCONNECT: null,
MESSAGE: null,
ERROR: null,
RECONNECT: null
});
class EventEmitter {
constructor() {
this.listeners = {};
}
on(event, callback) {
// Event names are guaranteed to be strings
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
}
// Usage
const emitter = new EventEmitter();
emitter.on(EVENTS.CONNECT, () => console.log('Connected'));
emitter.emit(EVENTS.CONNECT);Utilities for object property access and phone number validation.
const getByPath = require('fbjs/lib/getByPath');
const isInternationalPhoneNumber = require('fbjs/lib/isInternationalPhoneNumber');
/**
* Gets nested property by dot-notation path string
* @param object - Object to traverse
* @param path - Dot-notation path (e.g., 'user.profile.name')
* @returns Value at path or undefined if path doesn't exist
*/
function getByPath(object: Object, path: string): any;
/**
* Validates international phone number format
* @param number - Phone number string to validate
* @returns True if number appears to be valid international format
*/
function isInternationalPhoneNumber(number: string): boolean;Usage Examples:
const getByPath = require('fbjs/lib/getByPath');
const isInternationalPhoneNumber = require('fbjs/lib/isInternationalPhoneNumber');
// Nested object property access
const user = {
id: 123,
profile: {
personal: {
name: 'Alice Johnson',
age: 30
},
contact: {
email: 'alice@example.com',
phone: '+1-555-123-4567',
address: {
street: '123 Main St',
city: 'Springfield',
country: 'USA'
}
}
},
preferences: {
theme: 'dark',
notifications: {
email: true,
sms: false,
push: true
}
}
};
// Get nested values safely
const name = getByPath(user, 'profile.personal.name'); // 'Alice Johnson'
const email = getByPath(user, 'profile.contact.email'); // 'alice@example.com'
const city = getByPath(user, 'profile.contact.address.city'); // 'Springfield'
const theme = getByPath(user, 'preferences.theme'); // 'dark'
// Handle missing paths gracefully
const missing = getByPath(user, 'profile.social.twitter'); // undefined
const invalid = getByPath(user, 'invalid.path.here'); // undefined
// Dynamic path building
function getUserProperty(user, category, field) {
const path = `profile.${category}.${field}`;
return getByPath(user, path);
}
const userEmail = getUserProperty(user, 'contact', 'email');
const userAge = getUserProperty(user, 'personal', 'age');
// Form data extraction
const formPaths = [
'profile.personal.name',
'profile.contact.email',
'profile.contact.phone',
'preferences.notifications.email'
];
const formData = {};
formPaths.forEach(path => {
const value = getByPath(user, path);
if (value !== undefined) {
formData[path] = value;
}
});
// Configuration access
const config = {
api: {
endpoints: {
users: '/api/users',
products: '/api/products'
},
timeout: 5000,
retries: 3
},
ui: {
theme: {
primary: '#007bff',
secondary: '#6c757d'
}
}
};
const usersEndpoint = getByPath(config, 'api.endpoints.users');
const primaryColor = getByPath(config, 'ui.theme.primary');
// Phone number validation
const phoneNumbers = [
'+1-555-123-4567', // US format
'+44-20-7946-0958', // UK format
'+33-1-23-45-67-89', // France format
'+81-3-1234-5678', // Japan format
'555-123-4567', // Invalid (no country code)
'+1234567890123456', // Invalid (too long)
'not-a-phone' // Invalid (not a number)
];
phoneNumbers.forEach(number => {
const isValid = isInternationalPhoneNumber(number);
console.log(`${number}: ${isValid ? 'Valid' : 'Invalid'}`);
});
// User registration validation
function validateRegistration(userData) {
const errors = [];
const name = getByPath(userData, 'personal.name');
if (!name) {
errors.push('Name is required');
}
const email = getByPath(userData, 'contact.email');
if (!email || !email.includes('@')) {
errors.push('Valid email is required');
}
const phone = getByPath(userData, 'contact.phone');
if (phone && !isInternationalPhoneNumber(phone)) {
errors.push('Phone number must be in international format');
}
return {
isValid: errors.length === 0,
errors: errors
};
}
// API response processing
function processAPIResponse(response) {
return {
id: getByPath(response, 'data.user.id'),
name: getByPath(response, 'data.user.profile.displayName'),
avatar: getByPath(response, 'data.user.profile.avatar.url'),
isActive: getByPath(response, 'data.user.status.isActive'),
lastLogin: getByPath(response, 'data.user.activity.lastLoginDate'),
permissions: getByPath(response, 'data.user.permissions') || []
};
}
// Settings management
class Settings {
constructor(data) {
this.data = data;
}
get(path, defaultValue = null) {
const value = getByPath(this.data, path);
return value !== undefined ? value : defaultValue;
}
set(path, value) {
// Simple path setting (would need more complex logic for real implementation)
const keys = path.split('.');
let current = this.data;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
}Additional configuration and utility modules from Facebook's fork implementations.
const SiteData = require('fbjs/lib/SiteData');
const TokenizeUtil = require('fbjs/lib/TokenizeUtil');
const URI = require('fbjs/lib/URI');
const cssVar = require('fbjs/lib/cssVar');
/**
* Site configuration data object
* @type Object with site-specific configuration
*/
const SiteData: {
is_rtl: boolean; // Whether site uses right-to-left text direction
};
/**
* Text tokenization utilities
* @returns Punctuation regex pattern for tokenization
*/
function TokenizeUtil.getPunctuation(): string;
/**
* Simple URI wrapper class
* @constructor
* @param uri - URI string to parse
*/
class URI {
constructor(uri: string);
toString(): string;
}
/**
* CSS variable utility
* Gets CSS variable value by name from fbjs-css-vars
* @param name - CSS variable name
* @returns CSS variable value
* @throws Error via invariant if variable not found
*/
function cssVar(name: string): string;
/**
* Development warning utility (from __forks__)
* Logs warning to console if condition is false (no-op in production)
* @param condition - Condition to check (false triggers warning)
* @param format - Warning message format string with %s placeholders
* @param args - Values to substitute into format string
*/
function warning(condition: boolean, format: string, ...args: Array<any>): void;Usage Examples:
const SiteData = require('fbjs/lib/SiteData');
const TokenizeUtil = require('fbjs/lib/TokenizeUtil');
const URI = require('fbjs/lib/URI');
const cssVar = require('fbjs/lib/cssVar');
const warning = require('fbjs/lib/warning');
// Site configuration
if (SiteData.is_rtl) {
document.body.dir = 'rtl';
document.body.style.textAlign = 'right';
}
// Text tokenization
const punctuationPattern = TokenizeUtil.getPunctuation();
const tokenizer = new RegExp(punctuationPattern, 'g');
const tokens = text.split(tokenizer).filter(token => token.trim());
// URI handling
const uri = new URI('https://example.com/path?query=value#fragment');
const uriString = uri.toString(); // 'https://example.com/path?query=value#fragment'
// CSS variables
try {
const primaryColor = cssVar('primary-color');
element.style.color = primaryColor;
} catch (error) {
console.error('CSS variable not found:', error.message);
}
// Development warnings
function validateUser(user) {
warning(user.name, 'User name is missing, using ID as fallback');
warning(user.email, 'User email not provided, notifications disabled');
return {
id: user.id,
name: user.name || `User ${user.id}`,
email: user.email || null
};
}Development error handling and event listener utilities.
const ErrorUtils = require('fbjs/lib/ErrorUtils');
const EventListener = require('fbjs/lib/EventListener');
/**
* Error handling utilities for development and production
*/
const ErrorUtils: {
/**
* Global error handler registration
* @param handler - Function to handle uncaught errors
*/
setGlobalHandler(handler: (error: Error, isFatal: boolean) => void): void;
/**
* Reports error to error handling system
* @param error - Error object or message
* @param isFatal - Whether error is fatal to application
*/
reportError(error: Error | string, isFatal?: boolean): void;
/**
* Wraps function to catch and report errors
* @param fn - Function to wrap
* @param context - Optional context for error reporting
* @returns Wrapped function that catches errors
*/
guard(fn: Function, context?: string): Function;
/**
* Creates error boundary for React-like error handling
* @param component - Component or function to protect
* @param fallback - Fallback to use when error occurs
* @returns Protected component
*/
applyWithGuard(component: Function, fallback?: Function): Function;
};
/**
* Cross-browser event listener utilities
*/
const EventListener: {
/**
* Adds event listener with cross-browser compatibility
* @param target - DOM element or event target
* @param eventType - Event type string
* @param listener - Event handler function
* @param capture - Whether to use capture phase
* @returns Object with remove() method
*/
listen(
target: EventTarget,
eventType: string,
listener: (event: Event) => void,
capture?: boolean
): { remove(): void };
/**
* Adds one-time event listener
* @param target - DOM element or event target
* @param eventType - Event type string
* @param listener - Event handler function
* @returns Object with remove() method
*/
once(
target: EventTarget,
eventType: string,
listener: (event: Event) => void
): { remove(): void };
/**
* Captures events during capture phase
* @param target - DOM element or event target
* @param eventType - Event type string
* @param listener - Event handler function
* @returns Object with remove() method
*/
capture(
target: EventTarget,
eventType: string,
listener: (event: Event) => void
): { remove(): void };
};Usage Examples:
const ErrorUtils = require('fbjs/lib/ErrorUtils');
const EventListener = require('fbjs/lib/EventListener');
// Global error handling setup
ErrorUtils.setGlobalHandler((error, isFatal) => {
console.error('Global error caught:', error);
// Send to error reporting service
if (typeof window !== 'undefined' && window.errorReportingService) {
window.errorReportingService.report({
message: error.message,
stack: error.stack,
isFatal: isFatal,
timestamp: Date.now(),
userAgent: navigator.userAgent,
url: window.location.href
});
}
});
// Function wrapping for error safety
const safeAsyncFunction = ErrorUtils.guard(async function(userId) {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
return userData;
}, 'user-data-fetch');
// Event listener management
class ComponentManager {
constructor(element) {
this.element = element;
this.listeners = [];
}
addListener(eventType, handler) {
const listener = EventListener.listen(this.element, eventType, handler);
this.listeners.push(listener);
return listener;
}
addOneTimeListener(eventType, handler) {
const listener = EventListener.once(this.element, eventType, handler);
this.listeners.push(listener);
return listener;
}
destroy() {
// Clean up all listeners
this.listeners.forEach(listener => listener.remove());
this.listeners = [];
}
}
// Error boundary pattern
function createErrorBoundary(render, fallback) {
return ErrorUtils.applyWithGuard(render, fallback || function(error) {
return `<div class="error">Something went wrong: ${error.message}</div>`;
});
}
// Safe API calls with error reporting
async function safeApiCall(endpoint, options = {}) {
try {
const response = await fetch(endpoint, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
ErrorUtils.reportError(error, false);
throw error; // Re-throw for caller to handle
}
}
// Form validation with error handling
const safeFormValidator = ErrorUtils.guard(function(formData) {
const errors = [];
if (!formData.email) {
errors.push('Email is required');
} else if (!formData.email.includes('@')) {
errors.push('Invalid email format');
}
if (!formData.password) {
errors.push('Password is required');
} else if (formData.password.length < 8) {
errors.push('Password must be at least 8 characters');
}
return {
isValid: errors.length === 0,
errors: errors
};
}, 'form-validation');
// Event handling with cleanup
function setupPageEvents() {
const cleanupCallbacks = [];
// Navigation events
const navListener = EventListener.listen(document, 'click', (event) => {
if (event.target.matches('[data-nav]')) {
event.preventDefault();
const target = event.target.getAttribute('data-nav');
navigateTo(target);
}
});
cleanupCallbacks.push(() => navListener.remove());
// Form submission
const formListener = EventListener.listen(document, 'submit', (event) => {
if (event.target.matches('form[data-ajax]')) {
event.preventDefault();
handleAjaxForm(event.target);
}
});
cleanupCallbacks.push(() => formListener.remove());
// Keyboard shortcuts
const keyListener = EventListener.listen(document, 'keydown', (event) => {
if (event.ctrlKey && event.key === 's') {
event.preventDefault();
saveCurrentState();
}
});
cleanupCallbacks.push(() => keyListener.remove());
// Return cleanup function
return function cleanup() {
cleanupCallbacks.forEach(fn => fn());
};
}
// Error monitoring for critical sections
function monitorCriticalSection(name, fn) {
return ErrorUtils.guard(function(...args) {
const startTime = performance.now();
try {
const result = fn.apply(this, args);
// If it's a promise, handle async errors
if (result && typeof result.then === 'function') {
return result.catch(error => {
ErrorUtils.reportError(new Error(`Critical section '${name}' failed: ${error.message}`), true);
throw error;
});
}
return result;
} catch (error) {
const duration = performance.now() - startTime;
ErrorUtils.reportError(new Error(`Critical section '${name}' failed after ${duration}ms: ${error.message}`), true);
throw error;
}
}, `critical-${name}`);
}