haro
Version:
Haro is a modern immutable DataStore
1,331 lines (978 loc) • 34.9 kB
Markdown
# Haro
[](https://badge.fury.io/js/haro)
[](https://nodejs.org/)
[](https://opensource.org/licenses/BSD-3-Clause)
[](https://github.com/avoidwork/haro/actions)
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.
## Installation
### npm
```sh
npm install haro
```
### yarn
```sh
yarn add haro
```
### pnpm
```sh
pnpm add haro
```
## Usage
### Factory Function
```javascript
import { haro } from 'haro';
const store = haro(data, config);
```
### Class Constructor
```javascript
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: 'alice@company.com', department: 'Engineering' },
{ name: 'Bob', email: 'bob@company.com', department: 'Sales' }
], {
index: ['name', 'department'],
versioning: true
});
```
### Class Inheritance
```javascript
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);
}
}
```
## Parameters
### delimiter
**String** - Delimiter for composite indexes (default: `'|'`)
```javascript
const store = haro(null, { delimiter: '::' });
```
### id
**String** - Unique identifier for this store instance. Auto-generated if not provided.
```javascript
const store = haro(null, { id: 'user-cache' });
```
### immutable
**Boolean** - Return frozen/immutable objects for data safety (default: `false`)
```javascript
const store = haro(null, { immutable: true });
```
### index
**Array** - Fields to index for faster searches. Supports composite indexes using delimiter.
```javascript
const store = haro(null, {
index: ['name', 'email', 'name|department', 'department|role']
});
```
### key
**String** - Primary key field name (default: `'id'`)
```javascript
const store = haro(null, { key: 'userId' });
```
### versioning
**Boolean** - Enable MVCC-style versioning to track record changes (default: `false`)
```javascript
const store = haro(null, { versioning: true });
```
### Parameter Validation
The constructor validates configuration and provides helpful error messages:
```javascript
// 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');
}
```
## Interoperability
### Array Methods Compatibility
Haro provides Array-like methods for familiar data manipulation:
```javascript
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})`);
});
```
### Event-Driven Architecture
Compatible with event-driven patterns through lifecycle hooks:
```javascript
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);
}
}
```
## Testing
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
--------------|---------|----------|---------|---------|-------------------------
```
### Test Organization
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
### Running Tests
```bash
# 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
```
## Benchmarks
Haro includes comprehensive benchmark suites for performance analysis and comparison with other data store solutions.
### Latest Performance Results
**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
### Benchmark Categories
#### Basic Operations
- **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
#### Search & Query Operations
- **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
#### Advanced Features
- **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
### Running Benchmarks
```bash
# 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
```
### Performance Comparison with Native Structures
**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 Efficiency
**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
### Performance Tips
For optimal performance:
1. **Use indexes wisely** - Index fields you'll query frequently
2. **Choose appropriate key strategy** - Shorter keys perform better
3. **Batch operations** - Use batch() for multiple changes
4. **Consider immutable mode cost** - Only enable if needed for data safety
5. **Minimize version history** - Disable versioning if not required
6. **Use pagination** - Implement limit() for large result sets
7. **Leverage utility methods** - Use built-in clone, merge, freeze for safety
### Performance Indicators
* ✅ **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)
### Immutable vs Mutable Mode
**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.
## API Reference
### Properties
#### data
`{Map}` - Internal Map of records, indexed by key
```javascript
const store = haro();
console.log(store.data.size); // 0
```
#### delimiter
`{String}` - The delimiter used for composite indexes
```javascript
const store = haro(null, { delimiter: '|' });
console.log(store.delimiter); // '|'
```
#### id
`{String}` - Unique identifier for this store instance
```javascript
const store = haro(null, { id: 'my-store' });
console.log(store.id); // 'my-store'
```
#### immutable
`{Boolean}` - Whether the store returns immutable objects
```javascript
const store = haro(null, { immutable: true });
console.log(store.immutable); // true
```
#### index
`{Array}` - Array of indexed field names
```javascript
const store = haro(null, { index: ['name', 'email'] });
console.log(store.index); // ['name', 'email']
```
#### indexes
`{Map}` - Map of indexes containing Sets of record keys
```javascript
const store = haro();
console.log(store.indexes); // Map(0) {}
```
#### key
`{String}` - The primary key field name
```javascript
const store = haro(null, { key: 'userId' });
console.log(store.key); // 'userId'
```
#### registry
`{Array}` - Array of all record keys (read-only property)
```javascript
const store = haro();
store.set('key1', { name: 'Alice' });
console.log(store.registry); // ['key1']
```
#### size
`{Number}` - Number of records in the store (read-only property)
```javascript
const store = haro();
console.log(store.size); // 0
```
#### versions
`{Map}` - Map of version history (when versioning is enabled)
```javascript
const store = haro(null, { versioning: true });
console.log(store.versions); // Map(0) {}
```
#### versioning
`{Boolean}` - Whether versioning is enabled
```javascript
const store = haro(null, { versioning: true });
console.log(store.versioning); // true
```
### Methods
#### batch(array, type)
Performs batch operations on multiple records for efficient bulk processing.
**Parameters:**
- `array` `{Array}` - Array of records to process
- `type` `{String}` - Operation type: `'set'` or `'del'` (default: `'set'`)
**Returns:** `{Array}` Array of results from the batch operation
```javascript
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()
#### clear()
Removes all records, indexes, and versions from the store.
**Returns:** `{Haro}` Store instance for chaining
```javascript
store.clear();
console.log(store.size); // 0
```
**See also:** delete()
#### clone(arg)
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
```javascript
const original = { name: 'John', tags: ['user', 'admin'] };
const cloned = store.clone(original);
cloned.tags.push('new'); // original.tags is unchanged
```
#### delete(key, batch)
Deletes a record from the store and removes it from all indexes.
**Parameters:**
- `key` `{String}` - Key of record to delete
- `batch` `{Boolean}` - Whether this is part of a batch operation (default: `false`)
**Returns:** `{undefined}`
**Throws:** `{Error}` If record with the specified key is not found
```javascript
store.delete('user123');
```
**See also:** has(), clear(), batch()
#### dump(type)
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
```javascript
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()
#### each(array, fn)
Utility method to iterate over an array with a callback function.
**Parameters:**
- `array` `{Array}` - Array to iterate over
- `fn` `{Function}` - Function to call for each element
**Returns:** `{Array}` The original array for method chaining
```javascript
store.each([1, 2, 3], (item, index) => {
console.log(`Item ${index}: ${item}`);
});
```
#### entries()
Returns an iterator of [key, value] pairs for each record in the store.
**Returns:** `{Iterator}` Iterator of [key, value] pairs
```javascript
for (const [key, value] of store.entries()) {
console.log(`${key}:`, value);
}
```
**See also:** keys(), values()
#### filter(fn, raw)
Filters records using a predicate function, similar to Array.filter.
**Parameters:**
- `fn` `{Function}` - Predicate function to test each record
- `raw` `{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
```javascript
const adults = store.filter(record => record.age >= 18);
const recentUsers = store.filter(record =>
record.created > Date.now() - 86400000
);
```
**See also:** find(), where(), map()
#### find(where, raw)
Finds records matching the specified criteria using indexes for optimal performance.
**Parameters:**
- `where` `{Object}` - Object with field-value pairs to match
- `raw` `{Boolean}` - Whether to return raw data (default: `false`)
**Returns:** `{Array}` Array of matching records
```javascript
const engineers = store.find({ department: 'Engineering' });
const activeUsers = store.find({ status: 'active', role: 'user' });
```
**See also:** where(), search(), filter()
#### forEach(fn, ctx)
Executes a function for each record in the store, similar to Array.forEach.
**Parameters:**
- `fn` `{Function}` - Function to execute for each record
- `ctx` `{*}` - Context object to use as 'this' (default: store instance)
**Returns:** `{Haro}` Store instance for chaining
```javascript
store.forEach((record, key) => {
console.log(`${key}: ${record.name}`);
});
```
**See also:** map(), filter()
#### freeze(...args)
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
```javascript
const frozen = store.freeze(obj1, obj2, obj3);
// Returns Object.freeze([Object.freeze(obj1), ...])
```
#### get(key, raw)
Retrieves a record by its key.
**Parameters:**
- `key` `{String}` - Key of record to retrieve
- `raw` `{Boolean}` - Whether to return raw data (default: `false`)
**Returns:** `{Object|null}` The record if found, null if not found
```javascript
const user = store.get('user123');
const rawUser = store.get('user123', true);
```
**See also:** has(), set()
#### has(key)
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
```javascript
if (store.has('user123')) {
console.log('User exists');
}
```
**See also:** get(), delete()
#### keys()
Returns an iterator of all keys in the store.
**Returns:** `{Iterator}` Iterator of record keys
```javascript
for (const key of store.keys()) {
console.log('Key:', key);
}
```
**See also:** values(), entries()
#### limit(offset, max, raw)
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
```javascript
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()
#### map(fn, raw)
Transforms all records using a mapping function, similar to Array.map.
**Parameters:**
- `fn` `{Function}` - Function to transform each record
- `raw` `{Boolean}` - Whether to return raw data (default: `false`)
**Returns:** `{Array}` Array of transformed results
**Throws:** `{Error}` If fn is not a function
```javascript
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()
#### merge(a, b, override)
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
```javascript
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]
```
#### override(data, type)
Replaces all store data or indexes with new data for bulk operations.
**Parameters:**
- `data` `{Array}` - Data to replace with
- `type` `{String}` - Type of data: `'records'` or `'indexes'` (default: `'records'`)
**Returns:** `{Boolean}` True if operation succeeded
**Throws:** `{Error}` If type is invalid
```javascript
const backup = store.dump('records');
// Later restore from backup
store.override(backup, 'records');
```
**See also:** dump(), clear()
#### reduce(fn, accumulator)
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
```javascript
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()
#### reindex(index)
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
```javascript
store.reindex(); // Rebuild all indexes
store.reindex('name'); // Rebuild only name index
store.reindex(['name', 'email']); // Rebuild specific indexes
```
#### search(value, index, raw)
Searches for records containing a value across specified indexes.
**Parameters:**
- `value` `{Function|RegExp|*}` - Value to search for
- `index` `{String|Array}` - Index(es) to search in (optional)
- `raw` `{Boolean}` - Whether to return raw data (default: `false`)
**Returns:** `{Array}` Array of matching records
```javascript
// 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()
#### set(key, data, batch, override)
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 field
- `data` `{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
```javascript
// 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()
#### sort(fn, frozen)
Sorts all records using a comparator function.
**Parameters:**
- `fn` `{Function}` - Comparator function for sorting (a, b) => number
- `frozen` `{Boolean}` - Whether to return frozen records (default: `false`)
**Returns:** `{Array}` Sorted array of records
```javascript
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()
#### sortBy(index, raw)
Sorts records by a specific indexed field in ascending order.
**Parameters:**
- `index` `{String}` - Index field name to sort by
- `raw` `{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
```javascript
const byAge = store.sortBy('age');
const byName = store.sortBy('name');
const rawByDate = store.sortBy('created', true);
```
**See also:** sort(), find()
#### toArray()
Converts all store data to a plain array of records.
**Returns:** `{Array}` Array containing all records in the store
```javascript
const allRecords = store.toArray();
console.log(`Store contains ${allRecords.length} records`);
```
**See also:** limit(), sort()
#### uuid()
Generates a RFC4122 v4 UUID for record identification.
**Returns:** `{String}` UUID string in standard format
```javascript
const id = store.uuid(); // "f47ac10b-58cc-4372-a567-0e02b2c3d479"
```
#### values()
Returns an iterator of all values in the store.
**Returns:** `{Iterator}` Iterator of record values
```javascript
for (const record of store.values()) {
console.log(record.name);
}
```
**See also:** keys(), entries()
#### where(predicate, op)
Advanced filtering with predicate logic supporting AND/OR operations on arrays.
**Parameters:**
- `predicate` `{Object}` - Object with field-value pairs for filtering
- `op` `{String}` - Operator for array matching: `'||'` for OR, `'&&'` for AND (default: `'||'`)
**Returns:** `{Array}` Array of records matching the predicate criteria
```javascript
// 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()
## Lifecycle Hooks
Override these methods in subclasses for custom behavior:
### beforeBatch(args, type)
Executed before batch operations for preprocessing.
### beforeClear()
Executed before clear operation for cleanup preparation.
### beforeDelete(key, batch)
Executed before delete operation for validation or logging.
### beforeSet(key, data, batch, override)
Executed before set operation for data validation or transformation.
### onbatch(results, type)
Executed after batch operations for postprocessing.
### onclear()
Executed after clear operation for cleanup tasks.
### ondelete(key, batch)
Executed after delete operation for logging or notifications.
### onset(record, batch)
Executed after set operation for indexing or event emission.
## Examples
### User Management System
```javascript
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: 'alice@company.com',
name: 'Alice Johnson',
department: 'Engineering',
role: 'Senior Developer',
active: true
},
{
id: 'u2',
email: 'bob@company.com',
name: 'Bob Smith',
department: 'Engineering',
role: 'Team Lead',
active: true
},
{
id: 'u3',
email: 'carol@company.com',
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
```
### E-commerce Product Catalog
```javascript
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');
```
### Real-time Analytics Dashboard
```javascript
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'));
```
### Configuration Management
```javascript
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'));
```
## Performance
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
### Performance Characteristics
| 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 |
## License
Copyright (c) 2025 Jason Mulligan
Licensed under the BSD-3-Clause license.