A fast, flexible immutable DataStore for collections of records with indexing, versioning, and advanced querying capabilities. Provides a Map-like interface with powerful search and filtering features.
npm install haro
yarn add haro
pnpm add haro
import { haro } from 'haro';
const store = haro(data, config);
import { Haro } from 'haro';
// Create a store with indexes and versioning
const store = new Haro({
index: ['name', 'email', 'department'],
key: 'id',
versioning: true,
immutable: true
});
// Create store with initial data
const users = new Haro([
{ name: 'Alice', email: '[email protected]', department: 'Engineering' },
{ name: 'Bob', email: '[email protected]', department: 'Sales' }
], {
index: ['name', 'department'],
versioning: true
});
import { Haro } from 'haro';
class UserStore extends Haro {
constructor(config) {
super({
index: ['email', 'department', 'role'],
key: 'id',
versioning: true,
...config
});
}
beforeSet(key, data, batch, override) {
// Validate email format
if (data.email && !this.isValidEmail(data.email)) {
throw new Error('Invalid email format');
}
}
onset(record, batch) {
console.log(`User ${record.name} was added/updated`);
}
isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
String - Delimiter for composite indexes (default: '|'
)
const store = haro(null, { delimiter: '::' });
String - Unique identifier for this store instance. Auto-generated if not provided.
const store = haro(null, { id: 'user-cache' });
Boolean - Return frozen/immutable objects for data safety (default: false
)
const store = haro(null, { immutable: true });
Array - Fields to index for faster searches. Supports composite indexes using delimiter.
const store = haro(null, {
index: ['name', 'email', 'name|department', 'department|role']
});
String - Primary key field name (default: 'id'
)
const store = haro(null, { key: 'userId' });
Boolean - Enable MVCC-style versioning to track record changes (default: false
)
const store = haro(null, { versioning: true });
The constructor validates configuration and provides helpful error messages:
// Invalid index configuration will provide clear feedback
try {
const store = new Haro({ index: 'name' }); // Should be array
} catch (error) {
console.error(error.message); // Clear validation error
}
// Missing required configuration
try {
const store = haro([{id: 1}], { key: 'nonexistent' });
} catch (error) {
console.error('Key field validation error');
}
Haro provides Array-like methods for familiar data manipulation:
import { haro } from 'haro';
const store = haro([
{ id: 1, name: 'Alice', age: 30 },
{ id: 2, name: 'Bob', age: 25 },
{ id: 3, name: 'Charlie', age: 35 }
]);
// Use familiar Array methods
const adults = store.filter(record => record.age >= 30);
const names = store.map(record => record.name);
const totalAge = store.reduce((sum, record) => sum + record.age, 0);
store.forEach((record, key) => {
console.log(`${key}: ${record.name} (${record.age})`);
});
Compatible with event-driven patterns through lifecycle hooks:
class EventedStore extends Haro {
constructor(eventEmitter, config) {
super(config);
this.events = eventEmitter;
}
onset(record, batch) {
this.events.emit('record:created', record);
}
ondelete(key, batch) {
this.events.emit('record:deleted', key);
}
}
Haro maintains comprehensive test coverage across all features with 148 passing tests:
--------------|---------|----------|---------|---------|-------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-------------------------
All files | 100 | 96.95 | 100 | 100 |
constants.js | 100 | 100 | 100 | 100 |
haro.js | 100 | 96.94 | 100 | 100 | 205-208,667,678,972-976
--------------|---------|----------|---------|---------|-------------------------
The test suite is organized into focused areas:
- Basic CRUD Operations - Core data manipulation (set, get, delete, clear)
- Indexing - Index creation, composite indexes, and reindexing
- Searching & Filtering - find(), where(), search(), filter(), and sortBy() methods
- Immutable Mode - Data freezing and immutability guarantees
- Versioning - MVCC-style record versioning
- Lifecycle Hooks - beforeSet, onset, ondelete, etc.
- Utility Methods - clone(), merge(), limit(), map(), reduce(), etc.
- Error Handling - Validation and error scenarios
- Factory Function - haro() factory with various initialization patterns
# Run unit tests
npm test
# Run with coverage
npm run test:coverage
# Run integration tests
npm run test:integration
# Run performance benchmarks
npm run benchmark
Haro includes comprehensive benchmark suites for performance analysis and comparison with other data store solutions.
Overall Performance Summary:
- Total Tests: 572 tests across 9 categories
- Total Runtime: 1.6 minutes
- Best Performance: HAS operation (20,815,120 ops/second on 1,000 records)
- Memory Efficiency: Highly efficient with minimal overhead for typical workloads
- SET operations: Record creation, updates, overwrites
- GET operations: Single record retrieval, cache hits/misses
- DELETE operations: Record removal and index cleanup
- BATCH operations: Bulk insert/update/delete performance
Performance Highlights:
- SET operations: Up to 3.2M ops/sec for typical workloads
- GET operations: Up to 20M ops/sec with index lookups
- DELETE operations: Efficient cleanup with index maintenance
- BATCH operations: Optimized for bulk data manipulation
- INDEX queries: Using find() with indexed fields
- FILTER operations: Predicate-based filtering
- SEARCH operations: Text and regex searching
- WHERE clauses: Complex query conditions
Performance Highlights:
- Indexed FIND queries: Up to 64,594 ops/sec (1,000 records)
- FILTER operations: Up to 46,255 ops/sec
- Complex queries: Maintains good performance with multiple conditions
- Memory-efficient query processing
- VERSION tracking: Performance impact of versioning
- IMMUTABLE mode: Object freezing overhead
- COMPOSITE indexes: Multi-field index performance
- Memory usage: Efficient memory consumption patterns
- Utility operations: clone, merge, freeze, forEach performance
- Pagination: Limit-based result pagination
- Persistence: Data dump/restore operations
# Run all benchmarks
node benchmarks/index.js
# Run specific benchmark categories
node benchmarks/index.js --basic-only # Basic CRUD operations
node benchmarks/index.js --search-only # Search and query operations
node benchmarks/index.js --index-only # Index operations
node benchmarks/index.js --memory-only # Memory usage analysis
node benchmarks/index.js --comparison-only # vs native structures
node benchmarks/index.js --utilities-only # Utility operations
node benchmarks/index.js --pagination-only # Pagination performance
node benchmarks/index.js --persistence-only # Persistence operations
node benchmarks/index.js --immutable-only # Immutable vs mutable
# Run with memory analysis
node --expose-gc benchmarks/memory-usage.js
Storage Operations:
- Haro vs Map: Comparable performance for basic operations
- Haro vs Array: Slower for simple operations, faster for complex queries
- Haro vs Object: Trade-off between features and raw performance
Query Operations:
- Haro FIND (indexed): 64,594 ops/sec vs Array filter: 189,293 ops/sec
- Haro provides advanced query capabilities not available in native structures
- Memory overhead justified by feature richness
Memory Usage Comparison (50,000 records):
- Haro: 13.98 MB
- Map: 3.52 MB
- Object: 1.27 MB
- Array: 0.38 MB
Memory Analysis:
- Reasonable overhead for feature set provided
- Efficient index storage and maintenance
- Garbage collection friendly
For optimal performance:
- Use indexes wisely - Index fields you'll query frequently
- Choose appropriate key strategy - Shorter keys perform better
- Batch operations - Use batch() for multiple changes
- Consider immutable mode cost - Only enable if needed for data safety
- Minimize version history - Disable versioning if not required
- Use pagination - Implement limit() for large result sets
- Leverage utility methods - Use built-in clone, merge, freeze for safety
- âś… Indexed queries significantly outperform filters (64k vs 46k ops/sec)
- âś… Batch operations provide excellent bulk performance
- âś… Get operations consistently outperform set operations
- âś… Memory usage remains stable under load
- âś… Utility operations perform well (clone: 1.6M ops/sec)
Performance Impact:
- Creation: Minimal difference (1.27x faster mutable)
- Read operations: Comparable performance
- Write operations: Slight advantage to mutable mode
- Transformation operations: Significant performance cost in immutable mode
Recommendations:
- Use immutable mode for data safety in multi-consumer environments
- Use mutable mode for high-frequency write operations
- Consider the trade-off between safety and performance
See benchmarks/README.md
for complete documentation and advanced usage.
{Map}
- Internal Map of records, indexed by key
const store = haro();
console.log(store.data.size); // 0
{String}
- The delimiter used for composite indexes
const store = haro(null, { delimiter: '|' });
console.log(store.delimiter); // '|'
{String}
- Unique identifier for this store instance
const store = haro(null, { id: 'my-store' });
console.log(store.id); // 'my-store'
{Boolean}
- Whether the store returns immutable objects
const store = haro(null, { immutable: true });
console.log(store.immutable); // true
{Array}
- Array of indexed field names
const store = haro(null, { index: ['name', 'email'] });
console.log(store.index); // ['name', 'email']
{Map}
- Map of indexes containing Sets of record keys
const store = haro();
console.log(store.indexes); // Map(0) {}
{String}
- The primary key field name
const store = haro(null, { key: 'userId' });
console.log(store.key); // 'userId'
{Array}
- Array of all record keys (read-only property)
const store = haro();
store.set('key1', { name: 'Alice' });
console.log(store.registry); // ['key1']
{Number}
- Number of records in the store (read-only property)
const store = haro();
console.log(store.size); // 0
{Map}
- Map of version history (when versioning is enabled)
const store = haro(null, { versioning: true });
console.log(store.versions); // Map(0) {}
{Boolean}
- Whether versioning is enabled
const store = haro(null, { versioning: true });
console.log(store.versioning); // true
Performs batch operations on multiple records for efficient bulk processing.
Parameters:
array
{Array}
- Array of records to processtype
{String}
- Operation type:'set'
or'del'
(default:'set'
)
Returns: {Array}
Array of results from the batch operation
const results = store.batch([
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 28 }
], 'set');
// Delete multiple records
store.batch(['key1', 'key2'], 'del');
See also: set(), delete()
Removes all records, indexes, and versions from the store.
Returns: {Haro}
Store instance for chaining
store.clear();
console.log(store.size); // 0
See also: delete()
Creates a deep clone of the given value, handling objects, arrays, and primitives.
Parameters:
arg
{*}
- Value to clone (any type)
Returns: {*}
Deep clone of the argument
const original = { name: 'John', tags: ['user', 'admin'] };
const cloned = store.clone(original);
cloned.tags.push('new'); // original.tags is unchanged
Deletes a record from the store and removes it from all indexes.
Parameters:
key
{String}
- Key of record to deletebatch
{Boolean}
- Whether this is part of a batch operation (default:false
)
Returns: {undefined}
Throws: {Error}
If record with the specified key is not found
store.delete('user123');
See also: has(), clear(), batch()
Exports complete store data or indexes for persistence or debugging.
Parameters:
type
{String}
- Type of data to export:'records'
or'indexes'
(default:'records'
)
Returns: {Array}
Array of [key, value] pairs or serialized index structure
const records = store.dump('records');
const indexes = store.dump('indexes');
// Use for persistence or backup
fs.writeFileSync('backup.json', JSON.stringify(records));
See also: override()
Utility method to iterate over an array with a callback function.
Parameters:
array
{Array}
- Array to iterate overfn
{Function}
- Function to call for each element
Returns: {Array}
The original array for method chaining
store.each([1, 2, 3], (item, index) => {
console.log(`Item ${index}: ${item}`);
});
Returns an iterator of [key, value] pairs for each record in the store.
Returns: {Iterator}
Iterator of [key, value] pairs
for (const [key, value] of store.entries()) {
console.log(`${key}:`, value);
}
See also: keys(), values()
Filters records using a predicate function, similar to Array.filter.
Parameters:
fn
{Function}
- Predicate function to test each recordraw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of records that pass the predicate test
Throws: {Error}
If fn is not a function
const adults = store.filter(record => record.age >= 18);
const recentUsers = store.filter(record =>
record.created > Date.now() - 86400000
);
See also: find(), where(), map()
Finds records matching the specified criteria using indexes for optimal performance.
Parameters:
where
{Object}
- Object with field-value pairs to matchraw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of matching records
const engineers = store.find({ department: 'Engineering' });
const activeUsers = store.find({ status: 'active', role: 'user' });
See also: where(), search(), filter()
Executes a function for each record in the store, similar to Array.forEach.
Parameters:
fn
{Function}
- Function to execute for each recordctx
{*}
- Context object to use as 'this' (default: store instance)
Returns: {Haro}
Store instance for chaining
store.forEach((record, key) => {
console.log(`${key}: ${record.name}`);
});
See also: map(), filter()
Creates a frozen array from the given arguments for immutable data handling.
Parameters:
...args
{*}
- Arguments to freeze into an array
Returns: {Array}
Frozen array containing frozen arguments
const frozen = store.freeze(obj1, obj2, obj3);
// Returns Object.freeze([Object.freeze(obj1), ...])
Retrieves a record by its key.
Parameters:
key
{String}
- Key of record to retrieveraw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Object|null}
The record if found, null if not found
const user = store.get('user123');
const rawUser = store.get('user123', true);
See also: has(), set()
Checks if a record with the specified key exists in the store.
Parameters:
key
{String}
- Key to check for existence
Returns: {Boolean}
True if record exists, false otherwise
if (store.has('user123')) {
console.log('User exists');
}
See also: get(), delete()
Returns an iterator of all keys in the store.
Returns: {Iterator}
Iterator of record keys
for (const key of store.keys()) {
console.log('Key:', key);
}
See also: values(), entries()
Returns a limited subset of records with offset support for pagination.
Parameters:
offset
{Number}
- Number of records to skip (default:0
)max
{Number}
- Maximum number of records to return (default:0
)raw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of records within the specified range
const page1 = store.limit(0, 10); // First 10 records
const page2 = store.limit(10, 10); // Next 10 records
const page3 = store.limit(20, 10); // Records 21-30
See also: toArray(), sort()
Transforms all records using a mapping function, similar to Array.map.
Parameters:
fn
{Function}
- Function to transform each recordraw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of transformed results
Throws: {Error}
If fn is not a function
const names = store.map(record => record.name);
const summaries = store.map(record => ({
id: record.id,
name: record.name,
email: record.email
}));
See also: filter(), forEach()
Merges two values together with support for arrays and objects.
Parameters:
a
{*}
- First value (target)b
{*}
- Second value (source)override
{Boolean}
- Whether to override arrays instead of concatenating (default:false
)
Returns: {*}
Merged result
const merged = store.merge({a: 1}, {b: 2}); // {a: 1, b: 2}
const arrays = store.merge([1, 2], [3, 4]); // [1, 2, 3, 4]
const overridden = store.merge([1, 2], [3, 4], true); // [3, 4]
Replaces all store data or indexes with new data for bulk operations.
Parameters:
data
{Array}
- Data to replace withtype
{String}
- Type of data:'records'
or'indexes'
(default:'records'
)
Returns: {Boolean}
True if operation succeeded
Throws: {Error}
If type is invalid
const backup = store.dump('records');
// Later restore from backup
store.override(backup, 'records');
See also: dump(), clear()
Reduces all records to a single value using a reducer function.
Parameters:
fn
{Function}
- Reducer function (accumulator, value, key, store)accumulator
{*}
- Initial accumulator value (default:[]
)
Returns: {*}
Final reduced value
const totalAge = store.reduce((sum, record) => sum + record.age, 0);
const emailList = store.reduce((emails, record) => {
emails.push(record.email);
return emails;
}, []);
See also: map(), filter()
Rebuilds indexes for specified fields or all fields for data consistency.
Parameters:
index
{String|Array}
- Specific index field(s) to rebuild (optional)
Returns: {Haro}
Store instance for chaining
store.reindex(); // Rebuild all indexes
store.reindex('name'); // Rebuild only name index
store.reindex(['name', 'email']); // Rebuild specific indexes
Searches for records containing a value across specified indexes.
Parameters:
value
{Function|RegExp|*}
- Value to search forindex
{String|Array}
- Index(es) to search in (optional)raw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of matching records
// Function search
const results = store.search(key => key.includes('admin'));
// Regex search on specific index
const nameResults = store.search(/^john/i, 'name');
// Value search across all indexes
const emailResults = store.search('gmail.com', 'email');
See also: find(), where(), filter()
Sets or updates a record in the store with automatic indexing.
Parameters:
key
{String|null}
- Key for the record, or null to use record's key fielddata
{Object}
- Record data to set (default:{}
)batch
{Boolean}
- Whether this is part of a batch operation (default:false
)override
{Boolean}
- Whether to override existing data instead of merging (default:false
)
Returns: {Object}
The stored record
// Auto-generate key
const user = store.set(null, { name: 'John', age: 30 });
// Update existing record (merges by default)
const updated = store.set('user123', { age: 31 });
// Replace existing record completely
const replaced = store.set('user123', { name: 'Jane' }, false, true);
See also: get(), batch(), merge()
Sorts all records using a comparator function.
Parameters:
fn
{Function}
- Comparator function for sorting (a, b) => numberfrozen
{Boolean}
- Whether to return frozen records (default:false
)
Returns: {Array}
Sorted array of records
const byAge = store.sort((a, b) => a.age - b.age);
const byName = store.sort((a, b) => a.name.localeCompare(b.name));
const frozen = store.sort((a, b) => a.created - b.created, true);
See also: sortBy(), limit()
Sorts records by a specific indexed field in ascending order.
Parameters:
index
{String}
- Index field name to sort byraw
{Boolean}
- Whether to return raw data (default:false
)
Returns: {Array}
Array of records sorted by the specified field
Throws: {Error}
If index field is empty or invalid
const byAge = store.sortBy('age');
const byName = store.sortBy('name');
const rawByDate = store.sortBy('created', true);
See also: sort(), find()
Converts all store data to a plain array of records.
Returns: {Array}
Array containing all records in the store
const allRecords = store.toArray();
console.log(`Store contains ${allRecords.length} records`);
See also: limit(), sort()
Generates a RFC4122 v4 UUID for record identification.
Returns: {String}
UUID string in standard format
const id = store.uuid(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
Returns an iterator of all values in the store.
Returns: {Iterator}
Iterator of record values
for (const record of store.values()) {
console.log(record.name);
}
See also: keys(), entries()
Advanced filtering with predicate logic supporting AND/OR operations on arrays.
Parameters:
predicate
{Object}
- Object with field-value pairs for filteringop
{String}
- Operator for array matching:'||'
for OR,'&&'
for AND (default:'||'
)
Returns: {Array}
Array of records matching the predicate criteria
// Find records with tags containing 'admin' OR 'user'
const users = store.where({ tags: ['admin', 'user'] }, '||');
// Find records with ALL specified tags
const powerUsers = store.where({ tags: ['admin', 'power'] }, '&&');
// Regex matching
const companyEmails = store.where({ email: /^[^@]+@company\.com$/ });
// Array field matching
const multiDeptUsers = store.where({ departments: ['IT', 'HR'] });
See also: find(), filter(), search()
Override these methods in subclasses for custom behavior:
Executed before batch operations for preprocessing.
Executed before clear operation for cleanup preparation.
Executed before delete operation for validation or logging.
Executed before set operation for data validation or transformation.
Executed after batch operations for postprocessing.
Executed after clear operation for cleanup tasks.
Executed after delete operation for logging or notifications.
Executed after set operation for indexing or event emission.
import { haro } from 'haro';
const users = haro(null, {
index: ['email', 'department', 'role', 'department|role'],
key: 'id',
versioning: true,
immutable: true
});
// Add users with batch operation
users.batch([
{
id: 'u1',
email: '[email protected]',
name: 'Alice Johnson',
department: 'Engineering',
role: 'Senior Developer',
active: true
},
{
id: 'u2',
email: '[email protected]',
name: 'Bob Smith',
department: 'Engineering',
role: 'Team Lead',
active: true
},
{
id: 'u3',
email: '[email protected]',
name: 'Carol Davis',
department: 'Marketing',
role: 'Manager',
active: false
}
], 'set');
// Find by department
const engineers = users.find({ department: 'Engineering' });
// Complex queries with where()
const activeEngineers = users.where({
department: 'Engineering',
active: true
}, '&&');
// Search across multiple fields
const managers = users.search(/manager|lead/i, ['role']);
// Pagination for large datasets
const page1 = users.limit(0, 10);
const page2 = users.limit(10, 10);
// Update user with version tracking
const updated = users.set('u1', { role: 'Principal Developer' });
console.log(users.versions.get('u1')); // Previous versions
import { Haro } from 'haro';
class ProductCatalog extends Haro {
constructor() {
super({
index: ['category', 'brand', 'price', 'tags', 'category|brand'],
key: 'sku',
versioning: true
});
}
beforeSet(key, data, batch, override) {
// Validate required fields
if (!data.name || !data.price || !data.category) {
throw new Error('Missing required product fields');
}
// Normalize price
if (typeof data.price === 'string') {
data.price = parseFloat(data.price);
}
// Auto-generate SKU if not provided
if (!data.sku && !key) {
data.sku = this.generateSKU(data);
}
}
onset(record, batch) {
console.log(`Product ${record.name} (${record.sku}) updated`);
}
generateSKU(product) {
const prefix = product.category.substring(0, 3).toUpperCase();
const suffix = Date.now().toString().slice(-6);
return `${prefix}-${suffix}`;
}
// Custom business methods
findByPriceRange(min, max) {
return this.filter(product =>
product.price >= min && product.price <= max
);
}
searchProducts(query) {
// Search across multiple fields
const lowerQuery = query.toLowerCase();
return this.filter(product =>
product.name.toLowerCase().includes(lowerQuery) ||
product.description.toLowerCase().includes(lowerQuery) ||
product.tags.some(tag => tag.toLowerCase().includes(lowerQuery))
);
}
getRecommendations(sku, limit = 5) {
const product = this.get(sku);
if (!product) return [];
// Find similar products by category and brand
return this.find({
category: product.category,
brand: product.brand
})
.filter(p => p.sku !== sku)
.slice(0, limit);
}
}
const catalog = new ProductCatalog();
// Add products
catalog.batch([
{
sku: 'LAP-001',
name: 'MacBook Pro 16"',
category: 'Laptops',
brand: 'Apple',
price: 2499.99,
tags: ['professional', 'high-performance', 'creative'],
description: 'Powerful laptop for professionals'
},
{
sku: 'LAP-002',
name: 'ThinkPad X1 Carbon',
category: 'Laptops',
brand: 'Lenovo',
price: 1899.99,
tags: ['business', 'lightweight', 'durable'],
description: 'Business laptop with excellent build quality'
}
], 'set');
// Business queries
const laptops = catalog.find({ category: 'Laptops' });
const affordable = catalog.findByPriceRange(1000, 2000);
const searchResults = catalog.searchProducts('professional');
const recommendations = catalog.getRecommendations('LAP-001');
import { haro } from 'haro';
// Event tracking store
const events = haro(null, {
index: ['type', 'userId', 'timestamp', 'type|userId'],
key: 'id',
immutable: false // Allow mutations for performance
});
// Session tracking store
const sessions = haro(null, {
index: ['userId', 'status', 'lastActivity'],
key: 'sessionId',
versioning: true
});
// Analytics functions
function trackEvent(type, userId, data = {}) {
return events.set(null, {
id: events.uuid(),
type,
userId,
timestamp: Date.now(),
data,
...data
});
}
function getActiveUsers(minutes = 5) {
const threshold = Date.now() - (minutes * 60 * 1000);
return sessions.filter(session =>
session.status === 'active' &&
session.lastActivity > threshold
);
}
function getUserActivity(userId, hours = 24) {
const since = Date.now() - (hours * 60 * 60 * 1000);
return events.find({ userId })
.filter(event => event.timestamp > since)
.sort((a, b) => b.timestamp - a.timestamp);
}
function getEventStats(timeframe = 'hour') {
const now = Date.now();
const intervals = {
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000
};
const since = now - intervals[timeframe];
const recentEvents = events.filter(event => event.timestamp > since);
return recentEvents.reduce((stats, event) => {
stats[event.type] = (stats[event.type] || 0) + 1;
return stats;
}, {});
}
// Usage
trackEvent('page_view', 'user123', { page: '/dashboard' });
trackEvent('click', 'user123', { element: 'nav-menu' });
trackEvent('search', 'user456', { query: 'analytics' });
console.log('Active users:', getActiveUsers().length);
console.log('User activity:', getUserActivity('user123'));
console.log('Event stats:', getEventStats('hour'));
import { Haro } from 'haro';
class ConfigStore extends Haro {
constructor() {
super({
index: ['environment', 'service', 'type', 'environment|service'],
key: 'key',
versioning: true,
immutable: true
});
this.loadDefaults();
}
loadDefaults() {
this.batch([
{ key: 'db.host', value: 'localhost', environment: 'dev', type: 'database' },
{ key: 'db.port', value: 5432, environment: 'dev', type: 'database' },
{ key: 'api.timeout', value: 30000, environment: 'dev', type: 'api' },
{ key: 'db.host', value: 'prod-db.example.com', environment: 'prod', type: 'database' },
{ key: 'db.port', value: 5432, environment: 'prod', type: 'database' },
{ key: 'api.timeout', value: 10000, environment: 'prod', type: 'api' }
], 'set');
}
getConfig(key, environment = 'dev') {
const configs = this.find({ key, environment });
return configs.length > 0 ? configs[0].value : null;
}
getEnvironmentConfig(environment) {
return this.find({ environment }).reduce((config, item) => {
config[item.key] = item.value;
return config;
}, {});
}
updateConfig(key, value, environment = 'dev') {
const existing = this.find({ key, environment })[0];
if (existing) {
return this.set(key, { ...existing, value });
} else {
return this.set(key, { key, value, environment, type: 'custom' });
}
}
getDatabaseConfig(environment = 'dev') {
return this.find({ environment, type: 'database' });
}
}
const config = new ConfigStore();
// Get specific config
console.log(config.getConfig('db.host', 'prod')); // 'prod-db.example.com'
// Get all configs for environment
const devConfig = config.getEnvironmentConfig('dev');
// Update configuration
config.updateConfig('api.timeout', 45000, 'dev');
// Get configuration history
console.log(config.versions.get('api.timeout'));
Haro is optimized for:
- Fast indexing: O(1) lookups on indexed fields
- Efficient searches: Regex and function-based filtering with index acceleration
- Memory efficiency: Minimal overhead with optional immutability
- Batch operations: Optimized bulk inserts and updates
- Version tracking: Efficient MVCC-style versioning when enabled
Operation | Indexed | Non-Indexed | Notes |
---|---|---|---|
find() |
O(1) | O(n) | Use indexes for best performance |
get() |
O(1) | O(1) | Direct key lookup |
set() |
O(1) | O(1) | Includes index updates |
delete() |
O(1) | O(1) | Includes index cleanup |
filter() |
O(n) | O(n) | Full scan with predicate |
search() |
O(k) | O(n) | k = matching index entries |
Copyright (c) 2025 Jason Mulligan
Licensed under the BSD-3-Clause license.